Merge "Adding Capif provider"
diff --git a/provider/Dockerfile b/provider/Dockerfile
new file mode 100644
index 0000000..bfadfb8
--- /dev/null
+++ b/provider/Dockerfile
@@ -0,0 +1,38 @@
+#==================================================================================
+# Copyright (C) 2023: Nordix Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# This source code is part of the near-RT RIC (RAN Intelligent Controller)
+# platform project (RICP).
+#==================================================================================
+
+##
+## Build
+##
+FROM nexus3.o-ran-sc.org:10001/golang:1.19.2-bullseye AS build
+WORKDIR /app
+COPY go.mod .
+COPY go.sum .
+RUN go mod download
+COPY . .
+RUN go build -o /capifprovider
+##
+## Deploy
+##
+FROM gcr.io/distroless/base-debian11
+WORKDIR /
+## Copy from "build" stage
+COPY --from=build /capifprovider .
+USER nonroot:nonroot
+ENTRYPOINT ["/capifprovider"]
diff --git a/provider/generate.sh b/provider/generate.sh
new file mode 100755
index 0000000..36dfe59
--- /dev/null
+++ b/provider/generate.sh
@@ -0,0 +1,156 @@
+# -
+# ========================LICENSE_START=================================
+# O-RAN-SC
+# %%
+# Copyright (C) 2023: Nordix Foundation
+# %%
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ========================LICENSE_END===================================
+#
+
+cwd=$(pwd)
+
+mkdir -p specs
+
+curl https://www.3gpp.org/ftp/Specs/archive/29_series/29.222/29222-h60.zip -o specs/apidef.zip
+curl https://www.3gpp.org/ftp/Specs/archive/29_series/29.122/29122-h70.zip -o specs/common29122apidef.zip
+curl https://www.3gpp.org/ftp/Specs/archive/29_series/29.508/29508-h80.zip -o specs/common29508apidef.zip
+curl https://www.3gpp.org/ftp/Specs/archive/29_series/29.510/29510-h70.zip -o specs/common29510apidef.zip
+curl https://www.3gpp.org/ftp/Specs/archive/29_series/29.512/29512-h80.zip -o specs/common29512apidef.zip
+curl https://www.3gpp.org/ftp/Specs/archive/29_series/29.514/29514-h60.zip -o specs/common29514apidef.zip
+curl https://www.3gpp.org/ftp/Specs/archive/29_series/29.517/29517-h70.zip -o specs/common29517apidef.zip
+curl https://www.3gpp.org/ftp/Specs/archive/29_series/29.518/29518-h70.zip -o specs/common29518apidef.zip
+curl https://www.3gpp.org/ftp/Specs/archive/29_series/29.522/29522-h70.zip -o specs/common29522apidef.zip
+curl https://www.3gpp.org/ftp/Specs/archive/29_series/29.523/29523-h80.zip -o specs/common29523apidef.zip
+curl https://www.3gpp.org/ftp/Specs/archive/29_series/29.554/29554-h40.zip -o specs/common29554apidef.zip
+curl https://www.3gpp.org/ftp/Specs/archive/29_series/29.571/29571-h70.zip -o specs/common29571apidef.zip
+curl https://www.3gpp.org/ftp/Specs/archive/29_series/29.572/29572-h60.zip -o specs/common29572apidef.zip
+
+cd specs/
+
+jar xvf apidef.zip
+jar xvf common29122apidef.zip
+jar xvf common29508apidef.zip
+jar xvf common29510apidef.zip
+jar xvf common29512apidef.zip
+jar xvf common29514apidef.zip
+jar xvf common29517apidef.zip
+jar xvf common29518apidef.zip
+jar xvf common29522apidef.zip
+jar xvf common29523apidef.zip
+jar xvf common29554apidef.zip
+jar xvf common29571apidef.zip
+jar xvf common29572apidef.zip
+
+# Remove types that are not used by CAPIF that have dependencies to other specifications.
+sed -e 'H;x;/^\( *\)\n\1/{s/\n.*//;x;d;}' -e 's/.*//;x;/\CivicAddress/{s/^\( *\).*/ \1/;x;d;}' TS29571_CommonData.yaml >temp.yaml
+mv temp.yaml TS29571_CommonData.yaml
+sed -e 'H;x;/^\( *\)\n\1/{s/\n.*//;x;d;}' -e 's/.*//;x;/\ExternalMbsServiceArea/{s/^\( *\).*/ \1/;x;d;}' TS29571_CommonData.yaml >temp.yaml
+mv temp.yaml TS29571_CommonData.yaml
+sed -e 'H;x;/^\( *\)\n\1/{s/\n.*//;x;d;}' -e 's/.*//;x;/\GeographicArea/{s/^\( *\).*/ \1/;x;d;}' TS29571_CommonData.yaml >temp.yaml
+mv temp.yaml TS29571_CommonData.yaml
+sed -e 'H;x;/^\( *\)\n\1/{s/\n.*//;x;d;}' -e 's/.*//;x;/\GeoServiceArea/{s/^\( *\).*/ \1/;x;d;}' TS29571_CommonData.yaml >temp.yaml
+mv temp.yaml TS29571_CommonData.yaml
+sed -e 'H;x;/^\( *\)\n\1/{s/\n.*//;x;d;}' -e 's/.*//;x;/\MbsMediaComp/{s/^\( *\).*/ \1/;x;d;}' TS29571_CommonData.yaml >temp.yaml
+mv temp.yaml TS29571_CommonData.yaml
+sed -e 'H;x;/^\( *\)\n\1/{s/\n.*//;x;d;}' -e 's/.*//;x;/\MbsMediaCompRm/{s/^\( *\).*/ \1/;x;d;}' TS29571_CommonData.yaml >temp.yaml
+mv temp.yaml TS29571_CommonData.yaml
+sed -e 'H;x;/^\( *\)\n\1/{s/\n.*//;x;d;}' -e 's/.*//;x;/\MbsMediaInfo/{s/^\( *\).*/ \1/;x;d;}' TS29571_CommonData.yaml >temp.yaml
+mv temp.yaml TS29571_CommonData.yaml
+sed -e 'H;x;/^\( *\)\n\1/{s/\n.*//;x;d;}' -e 's/.*//;x;/\MbsServiceInfo/{s/^\( *\).*/ \1/;x;d;}' TS29571_CommonData.yaml >temp.yaml
+mv temp.yaml TS29571_CommonData.yaml
+sed -e 'H;x;/^\( *\)\n\1/{s/\n.*//;x;d;}' -e 's/.*//;x;/\MbsSession/{s/^\( *\).*/ \1/;x;d;}' TS29571_CommonData.yaml >temp.yaml
+mv temp.yaml TS29571_CommonData.yaml
+sed -e 'H;x;/^\( *\)\n\1/{s/\n.*//;x;d;}' -e 's/.*//;x;/\SpatialValidityCond/{s/^\( *\).*/ \1/;x;d;}' TS29571_CommonData.yaml >temp.yaml
+mv temp.yaml TS29571_CommonData.yaml
+
+# Remove attributes that can not be generated easily.
+sed '/accessTokenError.*/,+3d' TS29571_CommonData.yaml >temp.yaml
+mv temp.yaml TS29571_CommonData.yaml
+sed '/accessTokenRequest.*/,+3d' TS29571_CommonData.yaml >temp.yaml
+mv temp.yaml TS29571_CommonData.yaml
+
+
+sed '/oneOf.*/,+2d' TS29222_CAPIF_Publish_Service_API.yaml >temp.yaml
+mv temp.yaml TS29222_CAPIF_Publish_Service_API.yaml
+
+# Replace references to external specs that are collected to the common spec by the commoncollector
+# <replacements_start>
+cat TS29122_CommonData.yaml | sed 's/TS29572_Nlmf_Location/CommonData/g' > temp.yaml
+mv temp.yaml TS29122_CommonData.yaml
+cat TS29122_CommonData.yaml | sed 's/TS29554_Npcf_BDTPolicyControl/CommonData/g' > temp.yaml
+mv temp.yaml TS29122_CommonData.yaml
+cat TS29122_CommonData.yaml | sed 's/TS29514_Npcf_PolicyAuthorization/CommonData/g' > temp.yaml
+mv temp.yaml TS29122_CommonData.yaml
+cat TS29571_CommonData.yaml | sed 's/TS29514_Npcf_PolicyAuthorization/CommonData/g' > temp.yaml
+mv temp.yaml TS29571_CommonData.yaml
+cat TS29571_CommonData.yaml | sed 's/TS29572_Nlmf_Location/CommonData/g' > temp.yaml
+mv temp.yaml TS29571_CommonData.yaml
+cat TS29222_CAPIF_Publish_Service_API.yaml | sed 's/TS29572_Nlmf_Location/CommonData/g' > temp.yaml
+mv temp.yaml TS29222_CAPIF_Publish_Service_API.yaml
+# <new_replacement>
+
+# This spec has references to itself that need to be removed
+cat TS29571_CommonData.yaml | sed 's/TS29571_CommonData.yaml//g' > temp.yaml
+mv temp.yaml TS29571_CommonData.yaml
+
+cd $cwd
+
+echo "Fixing enums"
+cd internal/gentools/enumfixer
+go build .
+./enumfixer -apidir=../../../specs
+
+cd $cwd
+echo "Gathering common references"
+cd internal/gentools/commoncollector
+go build .
+./commoncollector -apidir=../../../specs
+
+cd $cwd
+echo "Fixing misc in specifications"
+cd internal/gentools/specificationfixer
+go build .
+./specificationfixer -apidir=../../../specs
+
+cd $cwd
+
+go install github.com/deepmap/oapi-codegen/cmd/oapi-codegen@v1.10.1
+PATH=$PATH:~/go/bin
+
+echo "Generating TS29122_CommonData"
+mkdir -p internal/common29122
+oapi-codegen --config gogeneratorspecs/common29122/generator_settings.yaml specs/TS29122_CommonData.yaml
+
+echo "Generating aggregated CommonData"
+mkdir -p internal/common
+oapi-codegen --config gogeneratorspecs/common/generator_settings.yaml specs/CommonData.yaml
+
+echo "Generating TS29571_CommonData"
+mkdir -p internal/common29571
+oapi-codegen --config gogeneratorspecs/common29571/generator_settings.yaml specs/TS29571_CommonData.yaml
+
+
+echo "Generating TS29222_CAPIF_API_Provider_Management_API"
+mkdir -p internal/providermanagementapi
+oapi-codegen --config gogeneratorspecs/providermanagementapi/generator_settings_types.yaml specs/TS29222_CAPIF_API_Provider_Management_API.yaml
+
+echo "Generating TS29222_CAPIF_Publish_Service_API"
+mkdir -p internal/publishserviceapi
+oapi-codegen --config gogeneratorspecs/publishserviceapi/generator_settings_types.yaml specs/TS29222_CAPIF_Publish_Service_API.yaml
+
+echo "Cleanup"
+rm -rf specs
+
+echo "Generating mocks."
+go generate ./...
\ No newline at end of file
diff --git a/provider/gogeneratorspecs/common/generator_settings.yaml b/provider/gogeneratorspecs/common/generator_settings.yaml
new file mode 100644
index 0000000..af302a1
--- /dev/null
+++ b/provider/gogeneratorspecs/common/generator_settings.yaml
@@ -0,0 +1,29 @@
+# -
+# ========================LICENSE_START=================================
+# O-RAN-SC
+# %%
+# Copyright (C) 2022: Nordix Foundation
+# %%
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ========================LICENSE_END===================================
+#
+
+output:
+ internal/common/common.gen.go
+package: common
+generate:
+ - types
+ - skip-prune
+ - spec
+import-mapping:
+ TS29571_CommonData.yaml: oransc.org/nonrtric/capifprov/internal/common29571
diff --git a/provider/gogeneratorspecs/common29122/generator_settings.yaml b/provider/gogeneratorspecs/common29122/generator_settings.yaml
new file mode 100644
index 0000000..69f77e2
--- /dev/null
+++ b/provider/gogeneratorspecs/common29122/generator_settings.yaml
@@ -0,0 +1,30 @@
+# -
+# ========================LICENSE_START=================================
+# O-RAN-SC
+# %%
+# Copyright (C) 2023: Nordix Foundation
+# %%
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ========================LICENSE_END===================================
+#
+
+output:
+ internal/common29122/common29122.gen.go
+package: common29122
+generate:
+ - types
+ - skip-prune
+ - spec
+import-mapping:
+ TS29571_CommonData.yaml: oransc.org/nonrtric/capifprov/internal/common29571
+ CommonData.yaml: oransc.org/nonrtric/capifprov/internal/common
\ No newline at end of file
diff --git a/provider/gogeneratorspecs/common29571/generator_settings.yaml b/provider/gogeneratorspecs/common29571/generator_settings.yaml
new file mode 100644
index 0000000..c83b629
--- /dev/null
+++ b/provider/gogeneratorspecs/common29571/generator_settings.yaml
@@ -0,0 +1,27 @@
+# -
+# ========================LICENSE_START=================================
+# O-RAN-SC
+# %%
+# Copyright (C) 2023: Nordix Foundation
+# %%
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ========================LICENSE_END===================================
+#
+
+output:
+ internal/common29571/common29571.gen.go
+package: common29571
+generate:
+ - types
+ - skip-prune
+ - spec
\ No newline at end of file
diff --git a/provider/gogeneratorspecs/providermanagementapi/generator_settings_types.yaml b/provider/gogeneratorspecs/providermanagementapi/generator_settings_types.yaml
new file mode 100644
index 0000000..fd9bd0a
--- /dev/null
+++ b/provider/gogeneratorspecs/providermanagementapi/generator_settings_types.yaml
@@ -0,0 +1,28 @@
+# -
+# ========================LICENSE_START=================================
+# O-RAN-SC
+# %%
+# Copyright (C) 2023: Nordix Foundation
+# %%
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ========================LICENSE_END===================================
+#
+
+output:
+ internal/providermanagementapi/providermanagementapi-types.gen.go
+package: providermanagementapi
+generate:
+ - types
+import-mapping:
+ TS29122_CommonData.yaml: oransc.org/nonrtric/capifprov/internal/common29122
+ TS29571_CommonData.yaml: oransc.org/nonrtric/capifprov/internal/common29571
\ No newline at end of file
diff --git a/provider/gogeneratorspecs/publishserviceapi/generator_settings_types.yaml b/provider/gogeneratorspecs/publishserviceapi/generator_settings_types.yaml
new file mode 100644
index 0000000..1a5f416
--- /dev/null
+++ b/provider/gogeneratorspecs/publishserviceapi/generator_settings_types.yaml
@@ -0,0 +1,29 @@
+# -
+# ========================LICENSE_START=================================
+# O-RAN-SC
+# %%
+# Copyright (C) 2023: Nordix Foundation
+# %%
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ========================LICENSE_END===================================
+#
+
+output:
+ internal/publishserviceapi/publishserviceapi-types.gen.go
+package: publishserviceapi
+generate:
+ - types
+import-mapping:
+ TS29122_CommonData.yaml: oransc.org/nonrtric/capifprov/internal/common29122
+ TS29571_CommonData.yaml: oransc.org/nonrtric/capifprov/internal/common29571
+ CommonData.yaml: oransc.org/nonrtric/capifprov/internal/common
\ No newline at end of file
diff --git a/provider/handler/common_handler.go b/provider/handler/common_handler.go
new file mode 100644
index 0000000..73952ee
--- /dev/null
+++ b/provider/handler/common_handler.go
@@ -0,0 +1,81 @@
+// -
+//
+// ========================LICENSE_START=================================
+// O-RAN-SC
+// %%
+// Copyright (C) 2023: Nordix Foundation
+// %%
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ========================LICENSE_END===================================
+package handler
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+)
+
+func makeRequest(method, url string, headers map[string]string, data interface{}) ([]byte, error) {
+ client := &http.Client{}
+
+ // Create a new HTTP request with the specified method and URL
+ req, err := http.NewRequest(method, url, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ // Set any headers specified in the headers map
+ for k, v := range headers {
+ req.Header.Set(k, v)
+ }
+
+ // If there is data to send, marshal it to JSON and set it as the request body
+ if data != nil {
+ jsonBytes, err := json.Marshal(data)
+ if err != nil {
+ return nil, err
+ }
+ req.Body = io.NopCloser(bytes.NewReader(jsonBytes))
+ }
+
+ // Send the request and get the response
+ if resp, err := client.Do(req); err == nil {
+ if isResponseSuccess(resp.StatusCode) {
+ defer resp.Body.Close()
+
+ // Read the response body
+ respBody, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return nil, err
+ }
+ return respBody, nil
+ } else {
+ return nil, getRequestError(resp)
+ }
+ } else {
+ return nil, err
+ }
+}
+
+func isResponseSuccess(statusCode int) bool {
+ return statusCode >= http.StatusOK && statusCode <= 299
+}
+
+func getRequestError(response *http.Response) error {
+ defer response.Body.Close()
+ responseData, _ := io.ReadAll(response.Body)
+
+ return fmt.Errorf("message: %v code: %v", string(responseData), response.StatusCode)
+}
diff --git a/provider/handler/getapi_handler.go b/provider/handler/getapi_handler.go
new file mode 100644
index 0000000..01c33e6
--- /dev/null
+++ b/provider/handler/getapi_handler.go
@@ -0,0 +1,80 @@
+// -
+//
+// ========================LICENSE_START=================================
+// O-RAN-SC
+// %%
+// Copyright (C) 2023: Nordix Foundation
+// %%
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ========================LICENSE_END===================================
+package handler
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+
+ log "github.com/sirupsen/logrus"
+
+ "github.com/labstack/echo/v4"
+ "oransc.org/nonrtric/capifprov/internal/publishserviceapi"
+)
+
+func GetApiRequest(server string) echo.HandlerFunc {
+ return func(c echo.Context) error {
+
+ aefId := c.FormValue("apfId")
+ if aefId == "" {
+ return c.Render(http.StatusOK, "getapi.html", map[string]interface{}{
+ "isResponse": false,
+ "isError": false,
+ })
+ }
+
+ //server format: http://localhost:8090
+ url := server + "/published-apis/v1/" + aefId + "/service-apis"
+ log.Infof("[Get API] to %v for aefId: %v", url, aefId)
+
+ headers := map[string]string{
+ "Content-Type": "text/plain",
+ }
+ resp, err := makeRequest("GET", url, headers, nil)
+ if err != nil {
+ log.Errorf("[Get API] %v", fmt.Sprintf("error: %v", err))
+ return c.Render(http.StatusBadRequest, "getapi.html", map[string]interface{}{
+ "response": fmt.Sprintf("error: %v", err),
+ "isError": true,
+ "isResponse": false,
+ })
+ }
+ log.Infof("[Get API] Response from service: %+v error: %v\n", string(resp), err)
+
+ var resAPIs []publishserviceapi.ServiceAPIDescription
+ err = json.Unmarshal(resp, &resAPIs)
+ if err != nil {
+ log.Error("[Get API] error unmarshaling parameter ServiceAPIDescription as JSON")
+ return c.Render(http.StatusBadRequest, "getapi.html", map[string]interface{}{
+ "isResponse": false,
+ "isError": true,
+ "response": "error unmarshaling parameter ServiceAPIDescription as JSON",
+ })
+ }
+
+ bytes, _ := json.Marshal(resAPIs)
+ log.Infof("[Get API] There are %v ServiceAPIDescription objects available", len(resAPIs))
+ return c.Render(http.StatusOK, "getapi.html", map[string]interface{}{
+ "isResponse": true,
+ "response": string(bytes),
+ })
+ }
+}
diff --git a/provider/handler/home_handler.go b/provider/handler/home_handler.go
new file mode 100644
index 0000000..619d624
--- /dev/null
+++ b/provider/handler/home_handler.go
@@ -0,0 +1,32 @@
+// -
+//
+// ========================LICENSE_START=================================
+// O-RAN-SC
+// %%
+// Copyright (C) 2023: Nordix Foundation
+// %%
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ========================LICENSE_END===================================
+package handler
+
+import (
+ "net/http"
+
+ "github.com/labstack/echo/v4"
+)
+
+func HomeHandler(c echo.Context) error {
+ return c.Render(http.StatusOK, "home.html", map[string]interface{}{
+ "name": "HOME",
+ })
+}
diff --git a/provider/handler/publishapi_handler.go b/provider/handler/publishapi_handler.go
new file mode 100644
index 0000000..99e6775
--- /dev/null
+++ b/provider/handler/publishapi_handler.go
@@ -0,0 +1,98 @@
+// -
+//
+// ========================LICENSE_START=================================
+// O-RAN-SC
+// %%
+// Copyright (C) 2023: Nordix Foundation
+// %%
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ========================LICENSE_END===================================
+package handler
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+
+ "github.com/labstack/echo/v4"
+ log "github.com/sirupsen/logrus"
+ "oransc.org/nonrtric/capifprov/internal/publishserviceapi"
+)
+
+func PublishapiHandler(c echo.Context) error {
+ return c.Render(http.StatusOK, "publishapi.html", map[string]interface{}{
+ "isError": false,
+ "isResponse": false,
+ })
+}
+
+func PublishApiFormHandler(server string) echo.HandlerFunc {
+ return func(c echo.Context) error {
+
+ apfId := c.FormValue("apfId")
+ if apfId == "" {
+ return c.Render(http.StatusBadRequest, "publishapi.html", map[string]interface{}{
+ "isError": true,
+ "isResponse": false,
+ "response": "field apfId is needed",
+ })
+ }
+
+ //server format: http://localhost:8090
+ url := server + "/published-apis/v1/" + apfId + "/service-apis"
+
+ log.Infof("[Publish API] url to capif core %v for aefId: %v", url, apfId)
+ var apiDescription publishserviceapi.ServiceAPIDescription
+
+ err := json.Unmarshal([]byte(c.FormValue("apiDescription")), &apiDescription)
+ if err != nil {
+ log.Error("[Publish API] error unmarshaling parameter ServiceAPIDescription as JSON")
+ return c.Render(http.StatusBadRequest, "publishapi.html", map[string]interface{}{
+ "isResponse": false,
+ "isError": true,
+ "response": "error unmarshaling parameter ServiceAPIDescription as JSON",
+ })
+ }
+
+ headers := map[string]string{
+ "Content-Type": "application/json",
+ }
+ resp, err := makeRequest("POST", url, headers, apiDescription)
+ if err != nil {
+ log.Errorf("[Publish API] %v", fmt.Sprintf("error: %v", err))
+ return c.Render(http.StatusBadRequest, "publishapi.html", map[string]interface{}{
+ "isResponse": false,
+ "isError": true,
+ "response": fmt.Sprintf("error: %v", err),
+ })
+ }
+
+ var resAPI publishserviceapi.ServiceAPIDescription
+ err = json.Unmarshal(resp, &resAPI)
+ if err != nil {
+ log.Error("[Publish API] error unmarshaling parameter ServiceAPIDescription as JSON")
+ return c.Render(http.StatusBadRequest, "publishapi.html", map[string]interface{}{
+ "isResponse": false,
+ "isError": true,
+ "response": "Error unmarshaling parameter ServiceAPIDescription as JSON",
+ })
+ }
+
+ bytes, _ := json.Marshal(resAPI)
+ log.Infof("[Publish API] API %v with the id: %v has been register", resAPI.ApiName, *resAPI.ApiId)
+ return c.Render(http.StatusOK, "publishapi.html", map[string]interface{}{
+ "isResponse": true,
+ "response": string(bytes),
+ })
+ }
+}
diff --git a/provider/handler/registration_handler.go b/provider/handler/registration_handler.go
new file mode 100644
index 0000000..0259999
--- /dev/null
+++ b/provider/handler/registration_handler.go
@@ -0,0 +1,88 @@
+// -
+//
+// ========================LICENSE_START=================================
+// O-RAN-SC
+// %%
+// Copyright (C) 2023: Nordix Foundation
+// %%
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ========================LICENSE_END===================================
+package handler
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+
+ "github.com/labstack/echo/v4"
+ log "github.com/sirupsen/logrus"
+ "oransc.org/nonrtric/capifprov/internal/providermanagementapi"
+)
+
+func RegistrationHandler(c echo.Context) error {
+ return c.Render(http.StatusOK, "registration.html", map[string]interface{}{
+ "isError": false,
+ "isResponse": false,
+ })
+}
+
+func RegistrationFormHandler(server string) echo.HandlerFunc {
+ return func(c echo.Context) error {
+
+ url := server + "/api-provider-management/v1/registrations"
+ log.Infof("[Register provider] url to capif core %v\n", url)
+
+ var newProvider providermanagementapi.APIProviderEnrolmentDetails
+ err := json.Unmarshal([]byte(c.FormValue("enrolmentDetails")), &newProvider)
+ if err != nil {
+ log.Error("[Register provider] error unmarshaling parameter enrolmentDetails as JSON")
+ return c.Render(http.StatusBadRequest, "registration.html", map[string]interface{}{
+ "isResponse": false,
+ "isError": true,
+ "response": "Error unmarshaling parameter enrolmentDetails as JSON",
+ })
+ }
+
+ headers := map[string]string{
+ "Content-Type": "application/json",
+ }
+ resp, err := makeRequest("POST", url, headers, newProvider)
+ if err != nil {
+ log.Errorf("[Register provider] %v", fmt.Sprintf("error: %v", err))
+ return c.Render(http.StatusBadRequest, "registration.html", map[string]interface{}{
+ "isResponse": false,
+ "isError": true,
+ "response": fmt.Sprintf("error: %v", err),
+ })
+ }
+
+ var resProvider providermanagementapi.APIProviderEnrolmentDetails
+ err = json.Unmarshal(resp, &resProvider)
+ if err != nil {
+ log.Error("[Register provider] error unmarshaling parameter enrolmentDetails as JSON")
+ return c.Render(http.StatusBadRequest, "registration.html", map[string]interface{}{
+ "isResponse": false,
+ "isError": true,
+ "response": "error unmarshaling parameter enrolmentDetails as JSON",
+ })
+ }
+
+ bytes, _ := json.Marshal(resProvider)
+ log.Infof("[Register provider] Api Provider domain %v has been register\n", resProvider.ApiProvDomId)
+ return c.Render(http.StatusOK, "registration.html", map[string]interface{}{
+ "isResponse": true,
+ "isError": false,
+ "response": string(bytes),
+ })
+ }
+}
diff --git a/provider/internal/gentools/commoncollector/commoncollector.go b/provider/internal/gentools/commoncollector/commoncollector.go
new file mode 100644
index 0000000..ab85e0a
--- /dev/null
+++ b/provider/internal/gentools/commoncollector/commoncollector.go
@@ -0,0 +1,119 @@
+// -
+// ========================LICENSE_START=================================
+// O-RAN-SC
+// %%
+// Copyright (C) 2022: Nordix Foundation
+// %%
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ========================LICENSE_END===================================
+//
+
+package main
+
+import (
+ "bufio"
+ "flag"
+ "io/ioutil"
+ "log"
+ "os"
+ "strings"
+
+ "gopkg.in/yaml.v2"
+)
+
+var apiDir *string
+var common = map[interface{}]interface{}{
+ "openapi": "3.0.0",
+ "info": map[interface{}]interface{}{
+ "title": "Common",
+ "version": "1.0.0",
+ },
+ "components": map[interface{}]interface{}{
+ "schemas": map[interface{}]interface{}{},
+ },
+}
+
+func main() {
+ apiDir = flag.String("apidir", "", "Directory containing API definitions to fix")
+ flag.Parse()
+
+ file, err := os.Open("definitions.txt")
+ if err != nil {
+ log.Fatalf("Error opening file: %v", err)
+ }
+ defer func(file *os.File) {
+ _ = file.Close()
+ }(file)
+
+ scanner := bufio.NewScanner(file)
+ components := common["components"]
+ cMap := components.(map[interface{}]interface{})
+ schemas := cMap["schemas"].(map[interface{}]interface{})
+ for scanner.Scan() {
+ name, data := getDependency(scanner.Text())
+ if name == "EthFlowDescription" {
+ changeToLocalReference("fDir", "FlowDirection", data)
+ }
+ if name == "ReportingInformation" {
+ changeToLocalReference("notifMethod", "NotificationMethod", data)
+ }
+ if name == "RelativeCartesianLocation" {
+ properties := data["properties"].(map[interface{}]interface{})
+ delete(properties, true)
+ data["required"] = remove(data["required"].([]interface{}), 1)
+ }
+ schemas[name] = data
+ }
+
+ if err := scanner.Err(); err != nil {
+ log.Fatal(err)
+ }
+
+ modCommon, err := yaml.Marshal(common)
+ if err != nil {
+ log.Fatalf("Marshal: #%v ", err)
+ }
+ err = ioutil.WriteFile(*apiDir+"/"+"CommonData.yaml", modCommon, 0644)
+ if err != nil {
+ log.Fatalf("Error writing yamlFile. #%v ", err)
+ }
+}
+
+func changeToLocalReference(attrname, refName string, data map[interface{}]interface{}) {
+ properties := data["properties"].(map[interface{}]interface{})
+ ref := properties[attrname].(map[interface{}]interface{})
+ ref["$ref"] = "#/components/schemas/" + refName
+}
+
+func getDependency(s string) (string, map[interface{}]interface{}) {
+ info := strings.Split(s, "#")
+ yamlFile, err := ioutil.ReadFile(*apiDir + "/" + info[0])
+ if err != nil {
+ log.Fatalf("Error reading yamlFile. #%v ", err)
+ }
+ m := make(map[string]interface{})
+ err = yaml.Unmarshal(yamlFile, m)
+ if err != nil {
+ log.Fatalf("Unmarshal: %v", err)
+ }
+ components := m["components"]
+ cMap := components.(map[interface{}]interface{})
+ schemas := cMap["schemas"].(map[interface{}]interface{})
+ component := strings.Split(info[1], "/")
+ dep := schemas[component[3]].(map[interface{}]interface{})
+ return component[3], dep
+}
+
+func remove(slice []interface{}, s int) []interface{} {
+ return append(slice[:s], slice[s+1:]...)
+}
diff --git a/provider/internal/gentools/commoncollector/definitions.txt b/provider/internal/gentools/commoncollector/definitions.txt
new file mode 100644
index 0000000..a2f3f25
--- /dev/null
+++ b/provider/internal/gentools/commoncollector/definitions.txt
@@ -0,0 +1,38 @@
+TS29508_Nsmf_EventExposure.yaml#/components/schemas/NotificationMethod
+TS29510_Nnrf_NFManagement.yaml#/components/schemas/Ipv4AddressRange
+TS29512_Npcf_SMPolicyControl.yaml#/components/schemas/FlowDirection
+TS29514_Npcf_PolicyAuthorization.yaml#/components/schemas/ContentVersion
+TS29514_Npcf_PolicyAuthorization.yaml#/components/schemas/EthFlowDescription
+TS29514_Npcf_PolicyAuthorization.yaml#/components/schemas/FlowDescription
+TS29514_Npcf_PolicyAuthorization.yaml#/components/schemas/TscaiInputContainer
+TS29517_Naf_EventExposure.yaml#/components/schemas/AddrFqdn
+TS29518_Namf_EventExposure.yaml#/components/schemas/CommunicationFailure
+TS29522_TrafficInfluence.yaml#/components/schemas/AfResultInfo
+TS29522_TrafficInfluence.yaml#/components/schemas/AfResultStatus
+TS29523_Npcf_EventExposure.yaml#/components/schemas/ReportingInformation
+TS29554_Npcf_BDTPolicyControl.yaml#/components/schemas/NetworkAreaInfo
+TS29572_Nlmf_Location.yaml#/components/schemas/Altitude
+TS29572_Nlmf_Location.yaml#/components/schemas/Angle
+TS29572_Nlmf_Location.yaml#/components/schemas/CivicAddress
+TS29572_Nlmf_Location.yaml#/components/schemas/Confidence
+TS29572_Nlmf_Location.yaml#/components/schemas/EllipsoidArc
+TS29572_Nlmf_Location.yaml#/components/schemas/GADShape
+TS29572_Nlmf_Location.yaml#/components/schemas/GeographicArea
+TS29572_Nlmf_Location.yaml#/components/schemas/GeographicalCoordinates
+TS29572_Nlmf_Location.yaml#/components/schemas/InnerRadius
+TS29572_Nlmf_Location.yaml#/components/schemas/Local2dPointUncertaintyEllipse
+TS29572_Nlmf_Location.yaml#/components/schemas/Local3dPointUncertaintyEllipsoid
+TS29572_Nlmf_Location.yaml#/components/schemas/LocalOrigin
+TS29572_Nlmf_Location.yaml#/components/schemas/Orientation
+TS29572_Nlmf_Location.yaml#/components/schemas/Point
+TS29572_Nlmf_Location.yaml#/components/schemas/PointAltitude
+TS29572_Nlmf_Location.yaml#/components/schemas/PointAltitudeUncertainty
+TS29572_Nlmf_Location.yaml#/components/schemas/PointList
+TS29572_Nlmf_Location.yaml#/components/schemas/PointUncertaintyCircle
+TS29572_Nlmf_Location.yaml#/components/schemas/PointUncertaintyEllipse
+TS29572_Nlmf_Location.yaml#/components/schemas/Polygon
+TS29572_Nlmf_Location.yaml#/components/schemas/RelativeCartesianLocation
+TS29572_Nlmf_Location.yaml#/components/schemas/Uncertainty
+TS29572_Nlmf_Location.yaml#/components/schemas/UncertaintyEllipsoid
+TS29572_Nlmf_Location.yaml#/components/schemas/SupportedGADShapes
+TS29572_Nlmf_Location.yaml#/components/schemas/UncertaintyEllipse
\ No newline at end of file
diff --git a/provider/internal/gentools/enumfixer/enumfixer.go b/provider/internal/gentools/enumfixer/enumfixer.go
new file mode 100644
index 0000000..e722875
--- /dev/null
+++ b/provider/internal/gentools/enumfixer/enumfixer.go
@@ -0,0 +1,117 @@
+// -
+// ========================LICENSE_START=================================
+// O-RAN-SC
+// %%
+// Copyright (C) 2022: Nordix Foundation
+// %%
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ========================LICENSE_END===================================
+//
+
+package main
+
+import (
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "os"
+ "path/filepath"
+ "reflect"
+ "strconv"
+ "strings"
+
+ "gopkg.in/yaml.v2"
+)
+
+type Enum struct {
+ Enum []string `yaml:"enum"`
+ Type string `yaml:"type"`
+ Description string `yaml:"description"`
+}
+
+func main() {
+ var apiDir = flag.String("apidir", "", "Directory containing API definitions to fix")
+ flag.Parse()
+ err := filepath.Walk(*apiDir, fixEnums)
+ if err != nil {
+ fmt.Println(err)
+ }
+}
+
+func fixEnums(path string, info os.FileInfo, _ error) error {
+ if !info.IsDir() && strings.HasSuffix(info.Name(), ".yaml") {
+ yamlFile, err := ioutil.ReadFile(path)
+ if err != nil {
+ log.Printf("yamlFile. Get err #%v ", err)
+ }
+ m := make(map[string]interface{})
+ err = yaml.Unmarshal(yamlFile, m)
+ if err != nil {
+ log.Fatalf("Unmarshal: %v", err)
+ }
+ components := m["components"]
+ if components != nil {
+ cMap := components.(map[interface{}]interface{})
+ if _, ok := cMap["schemas"].(map[interface{}]interface{}); ok {
+ schemas := cMap["schemas"].(map[interface{}]interface{})
+ for typeName, typeDef := range schemas {
+ tDMap := typeDef.(map[interface{}]interface{})
+ anyOf, ok := tDMap["anyOf"]
+ if ok {
+ aOSlice := anyOf.([]interface{})
+ correctEnum := Enum{}
+ mapInterface := aOSlice[0].(map[interface{}]interface{})
+ enumInterface := mapInterface["enum"]
+ if enumInterface != nil {
+ is := enumInterface.([]interface{})
+ var enumVals []string
+ for i := 0; i < len(is); i++ {
+ if reflect.TypeOf(is[i]).Kind() == reflect.String {
+ enumVals = append(enumVals, is[i].(string))
+
+ } else if reflect.TypeOf(is[1]).Kind() == reflect.Int {
+ enumVals = append(enumVals, strconv.Itoa(is[i].(int)))
+ }
+ }
+ correctEnum.Enum = enumVals
+ correctEnum.Type = "string"
+ description := tDMap["description"]
+ if description != nil {
+ correctEnum.Description = description.(string)
+ } else {
+ if aOSlice[1] != nil {
+ mapInterface = aOSlice[1].(map[interface{}]interface{})
+ description := mapInterface["description"]
+ if description != nil {
+ correctEnum.Description = description.(string)
+ }
+ }
+ }
+ schemas[typeName] = correctEnum
+ }
+ }
+ }
+ modM, err := yaml.Marshal(m)
+ if err != nil {
+ log.Printf("yamlFile. Get err #%v ", err)
+ }
+ err = ioutil.WriteFile(path, modM, 0644)
+ if err != nil {
+ log.Printf("yamlFile. Get err #%v ", err)
+ }
+ }
+ }
+ }
+ return nil
+}
diff --git a/provider/internal/gentools/specificationfixer/specificationfixer.go b/provider/internal/gentools/specificationfixer/specificationfixer.go
new file mode 100644
index 0000000..4257b65
--- /dev/null
+++ b/provider/internal/gentools/specificationfixer/specificationfixer.go
@@ -0,0 +1,80 @@
+// -
+// ========================LICENSE_START=================================
+// O-RAN-SC
+// %%
+// Copyright (C) 2022: Nordix Foundation
+// %%
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ========================LICENSE_END===================================
+//
+
+package main
+
+import (
+ "flag"
+ "io/ioutil"
+ "log"
+
+ "gopkg.in/yaml.v2"
+)
+
+var apiDir *string
+
+func main() {
+ apiDir = flag.String("apidir", "", "Directory containing API definitions to fix")
+ flag.Parse()
+
+ m := getData("TS29571_CommonData.yaml")
+ components := m["components"]
+ cMap := components.(map[interface{}]interface{})
+ schemas := cMap["schemas"].(map[interface{}]interface{})
+ snssaiExtensionData := schemas["SnssaiExtension"].(map[interface{}]interface{})
+ props := snssaiExtensionData["properties"].(map[interface{}]interface{})
+ wildcardSdData := props["wildcardSd"].(map[interface{}]interface{})
+ delete(wildcardSdData, "enum")
+
+ writeFile("TS29571_CommonData.yaml", m)
+
+ m = getData("TS29222_CAPIF_Security_API.yaml")
+ components = m["components"]
+ cMap = components.(map[interface{}]interface{})
+ schemas = cMap["schemas"].(map[interface{}]interface{})
+ accessTokenReq := schemas["AccessTokenReq"].(map[interface{}]interface{})
+ accessTokenReq["type"] = "object"
+
+ writeFile("TS29222_CAPIF_Security_API.yaml", m)
+}
+
+func getData(filename string) map[string]interface{} {
+ yamlFile, err := ioutil.ReadFile(*apiDir + "/" + filename)
+ if err != nil {
+ log.Fatalf("Error reading yamlFile. #%v ", err)
+ }
+ m := make(map[string]interface{})
+ err = yaml.Unmarshal(yamlFile, m)
+ if err != nil {
+ log.Fatalf("Unmarshal: %v", err)
+ }
+ return m
+}
+
+func writeFile(filename string, data map[string]interface{}) {
+ modCommon, err := yaml.Marshal(data)
+ if err != nil {
+ log.Fatalf("Marshal: #%v ", err)
+ }
+ err = ioutil.WriteFile(*apiDir+"/"+filename, modCommon, 0644)
+ if err != nil {
+ log.Fatalf("Error writing yamlFile. #%v ", err)
+ }
+}
diff --git a/provider/main.go b/provider/main.go
new file mode 100644
index 0000000..b7661ea
--- /dev/null
+++ b/provider/main.go
@@ -0,0 +1,89 @@
+// -
+// ========================LICENSE_START=================================
+// O-RAN-SC
+// %%
+// Copyright (C) 2023: Nordix Foundation
+// %%
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ========================LICENSE_END===================================
+//
+
+package main
+
+import (
+ "errors"
+ "flag"
+ "fmt"
+ "html/template"
+ "io"
+
+ log "github.com/sirupsen/logrus"
+
+ "github.com/labstack/echo/v4"
+ "oransc.org/nonrtric/capifprov/handler"
+)
+
+type TemplateRegistry struct {
+ templates map[string]*template.Template
+}
+
+func (t *TemplateRegistry) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
+ tmpl, ok := t.templates[name]
+ if !ok {
+ err := errors.New("Template not found -> " + name)
+ return err
+ }
+ return tmpl.ExecuteTemplate(w, "base", data)
+}
+
+func main() {
+
+ // Echo instance
+ e := echo.New()
+ e.Static("/", "view")
+ var capifCoreUrl string
+ flag.StringVar(&capifCoreUrl, "capifCoreUrl", "http://localhost:8090", "Url for CAPIF core")
+ var logLevelStr = flag.String("loglevel", "Info", "Log level")
+ var port = flag.Int("port", 9090, "Port for CAPIF Provider")
+
+ flag.Parse()
+
+ if loglevel, err := log.ParseLevel(*logLevelStr); err == nil {
+ log.SetLevel(loglevel)
+ }
+
+ templates := make(map[string]*template.Template)
+ templates["home.html"] = template.Must(template.ParseFiles("view/home.html", "view/base.html"))
+ templates["registration.html"] = template.Must(template.ParseFiles("view/registration.html", "view/base.html"))
+ templates["publishapi.html"] = template.Must(template.ParseFiles("view/publishapi.html", "view/base.html"))
+ templates["getapi.html"] = template.Must(template.ParseFiles("view/getapi.html", "view/base.html"))
+
+ e.Renderer = &TemplateRegistry{
+ templates: templates,
+ }
+
+ // Route => handler
+ e.GET("/", handler.HomeHandler)
+ e.POST("/", handler.HomeHandler)
+
+ e.GET("/registration", handler.RegistrationHandler)
+ e.POST("/registration", handler.RegistrationFormHandler(capifCoreUrl))
+
+ e.GET("/publishapi", handler.PublishapiHandler)
+ e.POST("/publishapi", handler.PublishApiFormHandler(capifCoreUrl))
+
+ e.GET("/getapi", handler.GetApiRequest(capifCoreUrl))
+
+ // Start the web server
+ e.Logger.Fatal(e.Start(fmt.Sprintf("0.0.0.0:%d", *port)))
+}
diff --git a/provider/view/base.html b/provider/view/base.html
new file mode 100644
index 0000000..7d6d2b2
--- /dev/null
+++ b/provider/view/base.html
@@ -0,0 +1,53 @@
+<!--
+ ========================LICENSE_START=================================
+ O-RAN-SC
+ %%
+ Copyright (C) 2023: Nordix Foundation
+ %%
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ ========================LICENSE_END===================================
+-->
+{{define "base"}}
+ <!DOCTYPE html>
+ <html lang="en">
+ <head>
+ <!-- Required meta tags -->
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+
+ <!-- CSS -->
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
+ <link rel="stylesheet" type="text/css" href="./css/style.css">
+ <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
+ <script language="JavaScript" type="text/javascript" src="./js/script.js"></script>
+
+ <title>{{template "title" .}}</title>
+ </head>
+ <body>
+ <main>
+ <div class="container py-4">
+ <header class="d-flex flex-wrap justify-content-center py-3 mb-4 border-bottom text-dark">
+ <div class="col-4 text-center">
+ <a class="text-body-emphasis mb-3 mb-md-0 me-md-auto link-body-emphasis text-decoration-none" href="/">
+ <span class="fs-2">CAPIF API Provider Domain</span>
+ </a>
+ </div>
+ </header>
+ {{template "body" .}}
+ </div>
+ </main>
+ <!-- JS -->
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4" crossorigin="anonymous"></script>
+ </body>
+ </html>
+{{end}}
\ No newline at end of file
diff --git a/provider/view/css/style.css b/provider/view/css/style.css
new file mode 100644
index 0000000..1654f0c
--- /dev/null
+++ b/provider/view/css/style.css
@@ -0,0 +1,41 @@
+/*
+ ========================LICENSE_START=================================
+ O-RAN-SC
+ %%
+ Copyright (C) 2023: Nordix Foundation
+ %%
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ ========================LICENSE_END===================================
+*/
+.callout {
+ padding: 20px;
+ margin: 20px 0;
+ border: 1px solid #eee;
+ border-left-width: 5px;
+ border-radius: 3px;
+}
+
+.bs-callout {
+ margin-top: -5px;
+}
+.callout-info {
+ border-left-color: #5bc0de;
+}
+
+.callout-info h4 {
+ color: #5bc0de;
+}
+
+.hiddenRow {
+ padding: 0 !important;
+}
\ No newline at end of file
diff --git a/provider/view/getapi.html b/provider/view/getapi.html
new file mode 100644
index 0000000..9ae04e3
--- /dev/null
+++ b/provider/view/getapi.html
@@ -0,0 +1,109 @@
+<!--
+ ========================LICENSE_START=================================
+ O-RAN-SC
+ %%
+ Copyright (C) 2023: Nordix Foundation
+ %%
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ ========================LICENSE_END===================================
+-->
+{{define "title"}}
+ CAPIF Provider | {{index . "name"}}
+{{end}}
+
+{{define "body"}}
+
+{{if .isResponse}}
+<div class="p-5 mb-4 bg-light rounded-3">
+ <div class="container-fluid py-5">
+ <h4 class="card-subtitle mb-3 text-body-secondary">Response from CAPIF core</h4>
+ <div class="callout callout-info">
+ <h5 class="card-subtitle mb-3 text-body-secondary">ServiceAPIDescription</h5>
+ <div id="response">
+ </div>
+ </div>
+
+ <div class="btns col-md-12 text-center">
+ <form action="/" method="GET">
+ <input class="btn btn-secondary" formaction="/" type="submit" value="Return to main page">
+ </form>
+ </div>
+ </div>
+ </div>
+ <script>
+ var htmlResponse = "{{.response}}"
+ const strData = JSON.parse(htmlResponse);
+ let out = "";
+ strData.forEach((api) => {
+ out += `
+ <h6>
+ ApiId:
+ <small id="ApiId" class="text-muted">${api.apiId}</small>
+ </h6>
+ <h6>
+ ApiName:
+ <small id="ApiName" class="text-muted">${api.apiName}</small>
+ </h6>
+ <h6>
+ Description:
+ <small id="Description" class="text-muted">${api.description}</small>
+ </h6>
+
+ <h6>AefProfiles:</h6>
+ <div id="responseTable" class="table-responsive">
+ <table class="table accordion">
+ <thead>
+ <tr>
+ <th scope="col">AefId</th>
+ <th scope="col">AefLocation</th>
+ <th scope="col">DomainName</th>
+ <th scope="col">Protocol</th>
+ <th scope="col">SecurityMethods</th>
+ </tr>
+ </thead>
+ <tbody id="data-output">
+ <!-- Prodcuts from javascript file in here. -->
+ ${printAefProfiles(api.aefProfiles)}
+ </tbody>
+ </table>
+ </div>
+ `;
+
+ document.querySelector("#response").innerHTML = out;
+ });
+ </script>
+{{- else}}
+ <div class="p-5 mb-4 bg-light rounded-3">
+ <div class="container-fluid py-5">
+ {{if .isError}}
+ <div class="alert alert-danger" role="alert">
+ {{.response}}
+ </div>
+ {{end}}
+ <h5 class="card-subtitle mb-3 text-body-secondary">API publishing functions> Get APIs</h5>
+ <form action="/getapi" method="GET">
+ <div class="mb-3">
+ <label for="apfId" class="form-label">ApfId:</label>
+ <input type="text" class="form-control" id="apfId" name="apfId" placeholder="apfId" required>
+ </div>
+ <div class="btns col-md-12 text-center">
+ <input class="btn btn-primary" type="submit" value="Submit">
+ <input class="btn btn-secondary" formaction="/" type="submit" value="Cancel" formnovalidate>
+ </div>
+ </form>
+ </div>
+ </div>
+{{- end}}
+{{end}}
+
+
diff --git a/provider/view/home.html b/provider/view/home.html
new file mode 100644
index 0000000..9d9ff55
--- /dev/null
+++ b/provider/view/home.html
@@ -0,0 +1,70 @@
+<!--
+ ========================LICENSE_START=================================
+ O-RAN-SC
+ %%
+ Copyright (C) 2023: Nordix Foundation
+ %%
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ ========================LICENSE_END===================================
+-->
+{{define "title"}}
+ CAPIF Provider
+{{end}}
+
+{{define "body"}}
+<div class="bd-example">
+<div class="accordion" id="accordionExample">
+ <div class="accordion-item">
+ <h2 class="accordion-header">
+ <button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
+ API management function
+ </button>
+ </h2>
+ <div id="collapseOne" class="accordion-collapse collapse show" data-bs-parent="#accordionExample">
+ <div class="accordion-body">
+ <ul>
+ <li><a href="/registration">Registers a new API Provider domain with API provider domain functions profiles.</a></li>
+ </ul>
+ </div>
+ </div>
+ </div>
+ <div class="accordion-item">
+ <h2 class="accordion-header">
+ <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
+ APF API publishing function
+ </button>
+ </h2>
+ <div id="collapseTwo" class="accordion-collapse collapse" data-bs-parent="#accordionExample">
+ <div class="accordion-body">
+ <ul>
+ <li><a href="/publishapi">Publish a new API</a></li>
+ <li><a href="/getapi">Retrieve all published APIs</a></li>
+ </ul>
+ </div>
+ </div>
+ </div>
+ <div class="accordion-item">
+ <h2 class="accordion-header">
+ <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
+ AEF API exposing function
+ </button>
+ </h2>
+ <div id="collapseThree" class="accordion-collapse collapse" data-bs-parent="#accordionExample">
+
+ </div>
+ </div>
+ </div>
+</div>
+{{end}}
+
+
diff --git a/provider/view/js/script.js b/provider/view/js/script.js
new file mode 100644
index 0000000..2b25652
--- /dev/null
+++ b/provider/view/js/script.js
@@ -0,0 +1,121 @@
+/*
+ ========================LICENSE_START=================================
+ O-RAN-SC
+ %%
+ Copyright (C) 2023: Nordix Foundation
+ %%
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ ========================LICENSE_END===================================
+*/
+const isObject = (value) => typeof value === "object" && value !== null
+
+function checkValue(value){
+ return (isObject(value) ? value : "");
+}
+
+function printResources(resources) {
+ let res = Object.values(checkValue(resources));
+ let out = `<p class="lead">Resources:</p><ul>`;
+ res.forEach((r) => {
+ out += `<li>
+ <p>
+ <strong>CommType:</strong> ${r.commType}
+ <strong>CustOpName:</strong> ${r.custOpName}
+ <strong>ResourceName:</strong> ${r.resourceName}
+ <strong>Uri:</strong> ${r.uri}
+ <strong>Description:</strong> ${r.description}
+ <strong>Operations:</strong> ${Object.values(checkValue(r.operations))}
+ </p>
+ </li>`;
+ });
+ out += `</ul>`;
+ return out;
+}
+
+function printCustomOperations(custOperations) {
+ let operations = Object.values(checkValue(custOperations));
+ let out = `<p class="lead"> Custom Operations:</p><ul>`;
+ operations.forEach((o) => {
+ out += `<li>
+ <p>
+ <strong>CommType:</strong> ${o.commType}
+ <strong>CustOpName:</strong> ${o.custOpName}
+ <strong>Description:</strong> ${o.description}
+ <strong>Operations:</strong> ${Object.values(checkValue(o.operations))}
+ </p>
+ </li>`;
+ });
+ out += `</ul>`;
+ return out;
+}
+
+function printVersions(versions) {
+ let vers = Object.values(checkValue(versions));
+ let out = `<p class="lead">Versions:</p><ul>`;
+ vers.forEach((v) => {
+ out += `<li>
+ <p>
+ <strong>ApiVersion:</strong> ${v.apiVersion}
+ ${printCustomOperations(v.custOperations)}
+ ${printResources(v.resources)}
+ </p>
+ </li>`;
+ });
+ out += `</ul>`;
+ return out;
+}
+
+function printInterfaceDescription(description) {
+ let interfaceDescriptions = Object.values(checkValue(description));
+ let out = `<p class="lead">Interface Description:</p><ul>`;
+ interfaceDescriptions.forEach((d) => {
+ out += `<li>
+ <p>
+ <strong>Ipv4Addr:</strong> ${d.ipv4Addr}
+ <strong>Ipv6Addr:</strong> ${d.ipv6Addr}
+ <strong>Port:</strong> ${d.port}
+ <strong>SecurityMethods:</strong> ${Object.values(checkValue(d.securityMethods))}
+ </p>
+ </li>`;
+ });
+ out += `</ul>`;
+ return out;
+}
+
+function printAefProfiles(aefProfiles){
+ let out = "";
+ let index = 0;
+ aefProfiles.forEach((aef) => {
+ out += `
+ <tr data-bs-toggle="collapse" data-bs-target="#r${index}">
+ <td>${aef.aefId}</td>
+ <td>${aef.aefLocation}</td>
+ <td>${aef.domainName}</td>
+ <td>${aef.protocol}</td>
+ <td>${Object.values(checkValue(aef.securityMethods))}</td>
+ </tr>
+ <tr class="collapse accordion-collapse" id="r${index}" data-bs-parent=".table">
+ <td colspan="5">
+ <div id="demo1">
+ ${printInterfaceDescription(aef.interfaceDescriptions)}
+ ${printVersions(aef.versions)}
+ </div>
+ </td>
+ </tr>
+ `;
+ index++;
+ });
+ return out;
+}
+
+
diff --git a/provider/view/publishapi.html b/provider/view/publishapi.html
new file mode 100644
index 0000000..3f71ed6
--- /dev/null
+++ b/provider/view/publishapi.html
@@ -0,0 +1,111 @@
+<!--
+ ========================LICENSE_START=================================
+ O-RAN-SC
+ %%
+ Copyright (C) 2023: Nordix Foundation
+ %%
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ ========================LICENSE_END===================================
+-->
+{{define "title"}}
+ CAPIF Provider
+{{end}}
+
+{{define "body"}}
+ {{if .isResponse}}
+ <div class="p-5 mb-4 bg-light rounded-3">
+ <div class="container-fluid py-5">
+ <h4 class="card-subtitle mb-3 text-body-secondary">Response from CAPIF core</h4>
+ <div class="callout callout-info">
+ <h5 class="card-subtitle mb-3 text-body-secondary">ServiceAPIDescription</h5>
+ <div id="response">
+ <h6>
+ ApiId:
+ <small id="ApiId" class="text-muted"></small>
+ </h6>
+ <h6>
+ ApiName:
+ <small id="ApiName" class="text-muted"></small>
+ </h6>
+ <h6>
+ Description:
+ <small id="Description" class="text-muted"></small>
+ </h6>
+
+ <h6>AefProfiles:</h6>
+ <div id="responseTable" class="table-responsive">
+ <table class="table accordion">
+ <thead>
+ <tr>
+ <th scope="col">AefId</th>
+ <th scope="col">AefLocation</th>
+ <th scope="col">DomainName</th>
+ <th scope="col">Protocol</th>
+ <th scope="col">SecurityMethods</th>
+ </tr>
+ </thead>
+ <tbody id="data-output">
+ <!-- Prodcuts from javascript file in here. -->
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+
+ <div class="btns col-md-12 text-center">
+ <form action="/" method="GET">
+ <input class="btn btn-secondary" formaction="/" type="submit" value="Return to main page">
+ </form>
+ </div>
+ </div>
+ </div>
+ <script>
+ var htmlResponse = "{{.response}}"
+ const strData = JSON.parse(htmlResponse);
+ document.getElementById("ApiId").innerHTML = strData.apiId;
+ document.getElementById("ApiName").innerHTML = strData.apiName;
+ document.getElementById("Description").innerHTML = strData.description;
+
+ let aefProfiles = Object.values(checkValue(strData.aefProfiles));
+ document.querySelector("#data-output").innerHTML = printAefProfiles(aefProfiles);
+ </script>
+ {{- else}}
+ <div class="p-5 mb-4 bg-light rounded-3">
+ <div class="container-fluid py-5">
+ {{if .isError}}
+ <div class="alert alert-danger" role="alert">
+ {{.response}}
+ </div>
+ {{end}}
+ <h5 class="card-subtitle mb-3 text-body-secondary">API publishing functions> Publish API</h5>
+ <form action="/publishapi" method="POST">
+ <div class="mb-3">
+ <label for="apfId" class="form-label">ApfId:</label>
+ <input type="text" class="form-control" id="apfId" name="apfId" placeholder="apfId" required>
+ </div>
+ <div class="mb-3">
+ <label for="apiDescription" class="form-label">ServiceAPIDescription:</label>
+ <textarea id="apiDescription" class="form-control" name="apiDescription" rows="10" cols="60"></textarea>
+ </div>
+
+ <div class="btns col-md-12 text-center">
+ <input class="btn btn-primary" type="submit" value="Submit">
+ <input class="btn btn-secondary" formaction="/" type="submit" value="Cancel" formnovalidate>
+ </div>
+ </form>
+ </div>
+ </div>
+ {{- end}}
+{{end}}
+
+
diff --git a/provider/view/registration.html b/provider/view/registration.html
new file mode 100644
index 0000000..d0e381d
--- /dev/null
+++ b/provider/view/registration.html
@@ -0,0 +1,119 @@
+<!--
+ ========================LICENSE_START=================================
+ O-RAN-SC
+ %%
+ Copyright (C) 2023: Nordix Foundation
+ %%
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ ========================LICENSE_END===================================
+-->
+{{define "title"}}
+ CAPIF Provider | {{index . "name"}}
+{{end}}
+
+{{define "body"}}
+{{if .isResponse}}
+ <div class="p-5 mb-4 bg-light rounded-3">
+ <div class="container-fluid py-5">
+ <h4 class="card-subtitle mb-3 text-body-secondary">Response from CAPIF core</h4>
+ <div class="callout callout-info">
+ <h5 class="card-subtitle mb-3 text-body-secondary">APIProviderEnrolmentDetails</h5>
+ <div id="response">
+ <h6>
+ ApiProvDomId:
+ <small id="ApiProvDomId" class="text-muted"></small>
+ </h6>
+ <h6>
+ ApiProvDomInfo:
+ <small id="ApiProvDomInfo" class="text-muted"></small>
+ </h6>
+ <h6>
+ RegSec:
+ <small id="RegSec" class="text-muted"></small>
+ </h6>
+ <h6>APIProviderFunctionDetails:</h6>
+
+ <div id="responseTable">
+ <table class="table table-striped">
+ <thead>
+ <tr>
+ <th scope="col">ApiProvFuncId</th>
+ <th scope="col">ApiProvFuncInfo</th>
+ <th scope="col">ApiProvFuncRole</th>
+ <th scope="col">RegistrationInformation</th>
+ </tr>
+ </thead>
+ <tbody id="data-output">
+ <!-- Prodcuts from javascript file in here. -->
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+
+ <div class="btns col-md-12 text-center">
+ <form action="/" method="GET">
+ <input class="btn btn-secondary" formaction="/" type="submit" value="Return to main page">
+ </form>
+ </div>
+ </div>
+ </div>
+ <script>
+
+ var htmlResponse = "{{.response}}"
+ const strData = JSON.parse(htmlResponse);
+ document.getElementById("ApiProvDomId").innerHTML = strData.apiProvDomId;
+ document.getElementById("ApiProvDomInfo").innerHTML = strData.apiProvDomInfo;
+ document.getElementById("RegSec").innerHTML = strData.regSec;
+
+ let functionsV = Object.values(strData.apiProvFuncs);
+ let out = "";
+ functionsV.forEach((f) => {
+ out += `
+ <tr>
+ <td>${f.apiProvFuncId}</td>
+ <td>${f.apiProvFuncInfo}</td>
+ <td>${f.apiProvFuncRole}</td>
+ <td>${Object.values(f.regInfo)}</td>
+ </tr>
+ `;
+ });
+
+ document.querySelector("#data-output").innerHTML = out;
+ </script>
+{{- else}}
+ <div class="p-5 mb-4 bg-light rounded-3">
+ <div class="container-fluid py-5">
+ {{if .isError}}
+ <div class="alert alert-danger" role="alert">
+ {{.response}}
+ </div>
+ {{end}}
+ <h5 class="card-subtitle mb-3 text-body-secondary">API management functions> Registrations</h5>
+ <form action="/registration" method="POST">
+ <div class="mb-3">
+ <label for="enrolmentDetails" class="form-label">APIProviderEnrolmentDetails:</label>
+ <textarea id="enrolmentDetails" class="form-control" name="enrolmentDetails" rows="10" cols="60" required></textarea>
+ </div>
+
+ <div class="btns col-md-12 text-center">
+ <input class="btn btn-primary" type="submit" value="Submit">
+ <input class="btn btn-secondary" formaction="/" type="submit" value="Cancel" formnovalidate>
+ </div>
+ </form>
+ </div>
+ </div>
+{{- end}}
+{{end}}
+
+