Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat/profile #114

Merged
merged 28 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7a9be0e
feat: add profile api schema
Alonza0314 Oct 15, 2024
fb313a9
feat: add profile model
Alonza0314 Oct 15, 2024
173a2ce
fix: rewrite the fileter example in flowrules
Alonza0314 Oct 23, 2024
8685fcb
feat: add openapi generator docker script
Alonza0314 Oct 24, 2024
f4932d5
feat: add backend profile api handler function and its corresponds ro…
Alonza0314 Oct 28, 2024
13431ff
feat: add a script for quickly compile frontend and run backend
Alonza0314 Oct 28, 2024
c35d1d2
feat: add search box and pager in profile mlist page
Alonza0314 Oct 30, 2024
fa34f51
feat: add a selection box in subscriberCreate form
Alonza0314 Oct 30, 2024
ec5da90
test: add a test file on profile DTO, and fix a small missing bugs on…
Alonza0314 Nov 1, 2024
ef65d6e
fix: solve the wrong profileName use in edit page(typo as name, shoul…
Alonza0314 Nov 1, 2024
6677c96
fix: solve golangci-lint problem
Alonza0314 Nov 1, 2024
c6fedfe
fix: resolve govet shadowing issue by avoiding variable(err) re-decla…
Alonza0314 Nov 1, 2024
fffae3a
feat: add tenantId into profileData, and set it as admin in default
Alonza0314 Nov 2, 2024
256568b
refactor: add a blank line between import and function declaration
Alonza0314 Nov 2, 2024
242306a
refactor: remove console.log and replace some error message from cons…
Alonza0314 Nov 2, 2024
fc72c1f
refactor: apply early return on flowRule and upSecurity function
Alonza0314 Nov 2, 2024
458309a
refactor: extract flowRule/upSecurity/chargingConfig as additional co…
Alonza0314 Nov 2, 2024
bff83f8
refactor: add a blank line between import and function declaration
yccodr Nov 5, 2024
e2a693f
chore: format
yccodr Nov 5, 2024
6d6a95b
chore: format
yccodr Nov 5, 2024
e1150e0
feat: show more friendly ui when error occurs
yccodr Nov 5, 2024
ba474d6
chore: early return on no profile found
yccodr Nov 5, 2024
d66ffb5
feat: make return message more clear on porfile part
Alonza0314 Nov 8, 2024
e503852
feat: improve profile confict and not found return message's behavior
Alonza0314 Nov 8, 2024
2249b7a
feat: improve profile db operation error message behavior
Alonza0314 Nov 8, 2024
91d6661
refactor: remove gpsi in profile
Alonza0314 Nov 8, 2024
8423dd7
fix: solve 404 error when refresh profile page
Alonza0314 Nov 8, 2024
cbc47fc
fix: remove gpsi in profile dto
Alonza0314 Nov 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 188 additions & 0 deletions backend/WebUI/api_webui.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ const (
userDataColl = "userData"
tenantDataColl = "tenantData"
identityDataColl = "subscriptionData.identityData"
profileListColl = "profileList" // store profile name and gpsi
profileDataColl = "profileData" // store profile data
)

var jwtKey = "" // for generating JWT
Expand Down Expand Up @@ -1952,3 +1954,189 @@ func OptionsSubscribers(c *gin.Context) {

c.JSON(http.StatusNoContent, gin.H{})
}

// Delete profile by profileName
func DeleteProfile(c *gin.Context) {
setCorsHeader(c)
logger.ProcLog.Infoln("Delete One Profile Data")

profileName := c.Param("profileName")
pf, err := mongoapi.RestfulAPIGetOne(profileListColl, bson.M{"profileName": profileName})
if err != nil {
logger.ProcLog.Errorf("DeleteProfile err: %+v", err)
c.JSON(http.StatusInternalServerError, gin.H{})
return
}
if len(pf) == 0 {
c.JSON(http.StatusNotFound, gin.H{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use models.problemDetails (from openapi pkg) to instead.

"cause": "Profile does not exist",
})
return
}

dbProfileOperation(profileName, "delete", nil)
c.JSON(http.StatusNoContent, gin.H{})
}

// Get profile list
func GetProfiles(c *gin.Context) {
setCorsHeader(c)
logger.ProcLog.Infoln("Get All Profiles List")

_, err := GetTenantId(c)
if err != nil {
logger.ProcLog.Errorln(err.Error())
c.JSON(http.StatusBadRequest, gin.H{
"cause": "Illegal Token",
})
return
}

var pfList []ProfileListIE = make([]ProfileListIE, 0)
profileList, err := mongoapi.RestfulAPIGetMany(profileListColl, bson.M{})
if err != nil {
logger.ProcLog.Errorf("GetProfiles err: %+v", err)
c.JSON(http.StatusInternalServerError, gin.H{})
return
}
for _, profile := range profileList {
profileName := profile["profileName"]
gpsi := profile["gpsi"]

tmp := ProfileListIE{
ProfileName: profileName.(string),
Gpsi: gpsi.(string),
}
pfList = append(pfList, tmp)
}
c.JSON(http.StatusOK, pfList)
}

// Get profile by profileName
func GetProfile(c *gin.Context) {
setCorsHeader(c)
logger.ProcLog.Infoln("Get One Profile Data")

profileName := c.Param("profileName")

profile, err := mongoapi.RestfulAPIGetOne(profileDataColl, bson.M{"profileName": profileName})
if err != nil {
logger.ProcLog.Errorf("GetProfile err: %+v", err)
c.JSON(http.StatusInternalServerError, gin.H{})
Alonza0314 marked this conversation as resolved.
Show resolved Hide resolved
return
}
var pf Profile
err = json.Unmarshal(mapToByte(profile), &pf)
if err != nil {
logger.ProcLog.Errorf("JSON Unmarshal err: %+v", err)
c.JSON(http.StatusInternalServerError, gin.H{})
return
Alonza0314 marked this conversation as resolved.
Show resolved Hide resolved
}
c.JSON(http.StatusOK, pf)
}

// Post profile
func PostProfile(c *gin.Context) {
setCorsHeader(c)
logger.ProcLog.Infoln("Post One Profile Data")

tokenStr := c.GetHeader("Token")
_, err := ParseJWT(tokenStr)
if err != nil {
logger.ProcLog.Errorln(err.Error())
c.JSON(http.StatusBadRequest, gin.H{
"cause": "Illegal Token",
})
return
}

var profile Profile
if err = c.ShouldBindJSON(&profile); err != nil {
logger.ProcLog.Errorf("PostProfile err: %+v", err)
c.JSON(http.StatusBadRequest, gin.H{
"cause": "JSON format incorrect",
})
return
}

pf, err := mongoapi.RestfulAPIGetOne(profileListColl, bson.M{"profileName": profile.ProfileName})
if err != nil {
logger.ProcLog.Errorf("GetProfile err: %+v", err)
c.JSON(http.StatusInternalServerError, gin.H{})
Alonza0314 marked this conversation as resolved.
Show resolved Hide resolved
return
}
if len(pf) != 0 {
c.JSON(http.StatusConflict, gin.H{
"cause": "Profile already exists",
})
return
}

logger.ProcLog.Infof("PostProfile: %+v", profile.ProfileName)
dbProfileOperation(profile.ProfileName, "post", &profile)
Alonza0314 marked this conversation as resolved.
Show resolved Hide resolved
c.JSON(http.StatusCreated, gin.H{})
}

// Put profile by profileName
func PutProfile(c *gin.Context) {
setCorsHeader(c)
logger.ProcLog.Infoln("Put One Profile Data")

profileName := c.Param("profileName")

var profile Profile
if err := c.ShouldBindJSON(&profile); err != nil {
logger.ProcLog.Errorf("PutProfile err: %+v", err)
c.JSON(http.StatusBadRequest, gin.H{})
Alonza0314 marked this conversation as resolved.
Show resolved Hide resolved
return
}

pf, err := mongoapi.RestfulAPIGetOne(profileListColl, bson.M{"profileName": profile.ProfileName})
if err != nil {
logger.ProcLog.Errorf("PutProfile err: %+v", err)
c.JSON(http.StatusInternalServerError, gin.H{})
Alonza0314 marked this conversation as resolved.
Show resolved Hide resolved
return
}
if len(pf) == 0 {
c.JSON(http.StatusNotFound, gin.H{
"cause": "Profile does not exist",
})
return
}

logger.ProcLog.Infof("PutProfile: %+v", profile.ProfileName)
dbProfileOperation(profileName, "put", &profile)
c.JSON(http.StatusNoContent, gin.H{})
Alonza0314 marked this conversation as resolved.
Show resolved Hide resolved
}

func dbProfileOperation(profileName string, method string, profile *Profile) {
Alonza0314 marked this conversation as resolved.
Show resolved Hide resolved
filter := bson.M{"profileName": profileName}

// Replace all data with new one
if method == "put" {
if err := mongoapi.RestfulAPIDeleteOne(profileDataColl, filter); err != nil {
logger.ProcLog.Errorf("PutSubscriberByID err: %+v", err)
}
} else if method == "delete" {
if err := mongoapi.RestfulAPIDeleteOne(profileListColl, filter); err != nil {
logger.ProcLog.Errorf("DeleteSubscriberByID err: %+v", err)
}
if err := mongoapi.RestfulAPIDeleteOne(profileDataColl, filter); err != nil {
logger.ProcLog.Errorf("DeleteSubscriberByID err: %+v", err)
}
}
if method == "post" || method == "put" {
profileListIE := ProfileListIE{
ProfileName: profileName,
Gpsi: "",
}
profileListIEBsonM := toBsonM(profileListIE)
profileBsonM := toBsonM(profile)
if _, err := mongoapi.RestfulAPIPost(profileListColl, filter, profileListIEBsonM); err != nil {
logger.ProcLog.Errorf("PutSubscriberByID err: %+v", err)
}
if _, err := mongoapi.RestfulAPIPost(profileDataColl, filter, profileBsonM); err != nil {
logger.ProcLog.Errorf("PutSubscriberByID err: %+v", err)
}
}
}
15 changes: 15 additions & 0 deletions backend/WebUI/model_profile_data.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package WebUI

import "github.com/free5gc/openapi/models"

type Profile struct {
ProfileName string `json:"profileName"`
AccessAndMobilitySubscriptionData models.AccessAndMobilitySubscriptionData `json:"AccessAndMobilitySubscriptionData"`
SessionManagementSubscriptionData []models.SessionManagementSubscriptionData `json:"SessionManagementSubscriptionData"`
SmfSelectionSubscriptionData models.SmfSelectionSubscriptionData `json:"SmfSelectionSubscriptionData"`
AmPolicyData models.AmPolicyData `json:"AmPolicyData"`
SmPolicyData models.SmPolicyData `json:"SmPolicyData"`
FlowRules []FlowRule `json:"FlowRules"`
QosFlows []QosFlow `json:"QosFlows"`
ChargingDatas []ChargingData
}
6 changes: 6 additions & 0 deletions backend/WebUI/model_profile_list_ie.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package WebUI

type ProfileListIE struct {
ProfileName string `json:"profileName"`
Gpsi string `json:"gpsi"`
}
35 changes: 35 additions & 0 deletions backend/WebUI/routers.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,4 +250,39 @@ var routes = Routes{
"/verify-staticip",
VerifyStaticIP,
},

{
"Delete Profile",
http.MethodDelete,
"/profile/:profileName",
DeleteProfile,
},

{
"Get Profile List",
http.MethodGet,
"/profile",
GetProfiles,
},

{
"Get Profile",
http.MethodGet,
"/profile/:profileName",
GetProfile,
},

{
"Post Profile",
http.MethodPost,
"/profile",
PostProfile,
},

{
"Put Profile",
http.MethodPut,
"/profile/:profileName",
PutProfile,
},
}
102 changes: 100 additions & 2 deletions free5gc-Webconsole.postman_collection.json

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions frontend/openapi-generator-docker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#! /bin/bash

# prerequisites
# - docker

# use Docker to run OpenAPI Generator
docker run --rm -v $PWD:/local openapitools/openapi-generator-cli generate -i /local/webconsole.yaml -g typescript-axios -o /local/src/api

# replace Time with Date in the file
sed 's/: Time/: Date/g' /local/src/api/api.ts > /local/src/api/api.ts.mod
mv /local/src/api/api.ts.mod /local/src/api/api.ts # rename the replaced file to the original file
36 changes: 35 additions & 1 deletion frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ import ChangePassword from "./pages/ChangePassword";
import ChargingTable from "./pages/Charging/ChargingTable";
import { ProtectedRoute } from "./ProtectedRoute";
import { LoginContext, User } from "./LoginContext";

import ProfileList from "./pages/ProfileList";
import ProfileCreate from "./pages/ProfileCreate";
import ProfileRead from "./pages/ProfileRead";
export default function App() {
Alonza0314 marked this conversation as resolved.
Show resolved Hide resolved
const [user, setUser] = useState<User | null>(() => {
// retrieve from local storage on initial load (if available)
Expand Down Expand Up @@ -182,6 +184,38 @@ export default function App() {
</ProtectedRoute>
}
/>
<Route
path="/profile"
element={
<ProtectedRoute>
<ProfileList />
</ProtectedRoute>
}
/>
<Route
path="/profile/create"
element={
<ProtectedRoute>
<ProfileCreate />
</ProtectedRoute>
}
/>
<Route
path="/profile/create/:profileName"
element={
<ProtectedRoute>
<ProfileCreate />
</ProtectedRoute>
}
/>
<Route
path="/profile/:profileName"
element={
<ProtectedRoute>
<ProfileRead />
</ProtectedRoute>
}
/>
</Routes>
</BrowserRouter>
</LoginContext.Provider>
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/ListItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import PhoneAndroid from "@mui/icons-material/PhoneAndroid";
import FontDownload from "@mui/icons-material/FontDownload";
import SupervisorAccountOutlinedIcon from "@mui/icons-material/SupervisorAccountOutlined";
import AttachMoneyOutlinedIcon from "@mui/icons-material/AttachMoneyOutlined";
import PersonIcon from "@mui/icons-material/Person";

import { Link } from "react-router-dom";
import { LoginContext } from "./LoginContext";
Expand Down Expand Up @@ -43,6 +44,14 @@ export const MainListItems = () => {
<ListItemText primary="SUBSCRIBERS" />
</ListItemButton>
</Link>
<Link to="/profile" style={{ color: "inherit", textDecoration: "inherit" }}>
<ListItemButton>
<ListItemIcon>
<PersonIcon />
</ListItemIcon>
<ListItemText primary="PROFILE" />
</ListItemButton>
</Link>
<Link to="/analysis" style={{ color: "inherit", textDecoration: "inherit" }}>
<ListItemButton>
<ListItemIcon>
Expand Down
Loading
Loading