Revize ed410564
Přidáno uživatelem Michal Schwob před téměř 3 roky(ů)
backend/docker-compose.yml | ||
---|---|---|
19 | 19 |
app: # Spring boot application |
20 | 20 |
build: . |
21 | 21 |
container_name: app-backend # name of the container |
22 |
image: schwobik/backend-app:1.7
|
|
22 |
image: schwobik/backend-app:1.8
|
|
23 | 23 |
ports: |
24 | 24 |
- "8080:8080" # expose port 8080 out of the docker container do the local machine |
25 | 25 |
depends_on: |
... | ... | |
31 | 31 |
# Since our Dockerfile for web-server is located in react-app folder, our build context is ./react-app |
32 | 32 |
build: ../frontend |
33 | 33 |
container_name: frontend |
34 |
image: schwobik/frontend-app:1.7
|
|
34 |
image: schwobik/frontend-app:1.8
|
|
35 | 35 |
ports: |
36 | 36 |
- "80:80" |
37 | 37 |
|
backend/src/main/java/cz/zcu/kiv/backendapi/config/CorsConfig.java | ||
---|---|---|
14 | 14 |
public void addCorsMappings(CorsRegistry registry) { |
15 | 15 |
registry.addMapping("/**") |
16 | 16 |
.allowedOrigins("*") |
17 |
.allowedMethods("GET", "POST", "DELETE", "PUT", "OPTIONS"); |
|
17 |
.allowedMethods("GET", "POST", "DELETE", "PUT", "OPTIONS", "PATCH");
|
|
18 | 18 |
} |
19 | 19 |
} |
backend/src/main/java/cz/zcu/kiv/backendapi/security/SecurityConfig.java | ||
---|---|---|
21 | 21 |
import org.springframework.web.cors.CorsConfigurationSource; |
22 | 22 |
import org.springframework.web.cors.UrlBasedCorsConfigurationSource; |
23 | 23 |
|
24 |
import java.util.Arrays; |
|
25 |
import java.util.Collections; |
|
24 | 26 |
import java.util.HashMap; |
25 | 27 |
import java.util.Map; |
26 | 28 |
|
... | ... | |
124 | 126 |
@Bean |
125 | 127 |
CorsConfigurationSource corsConfigurationSource() { |
126 | 128 |
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); |
127 |
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues()); |
|
129 |
CorsConfiguration corsConfiguration = new CorsConfiguration().applyPermitDefaultValues(); |
|
130 |
corsConfiguration.setAllowedMethods(java.util.List.of(HttpMethod.GET.name(), HttpMethod.HEAD.name(), HttpMethod.POST.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name(), HttpMethod.PUT.name())); |
|
131 |
source.registerCorsConfiguration("/**", corsConfiguration); |
|
128 | 132 |
return source; |
129 | 133 |
} |
130 | 134 |
} |
frontend/src/features/Administration/Administration.tsx | ||
---|---|---|
19 | 19 |
import { |
20 | 20 |
clear, |
21 | 21 |
consumeError, |
22 |
resetIsRequestCompleted, |
|
22 | 23 |
setLoading, |
23 | 24 |
setSelectedUser |
24 | 25 |
} from "./userDetailSlice" |
... | ... | |
34 | 35 |
const loading = useSelector((state: RootState) => state.usersDetail.loading) |
35 | 36 |
const apiError = useSelector((state: RootState) => state.usersDetail.error) |
36 | 37 |
const selectedUser = useSelector((state: RootState) => state.usersDetail.selectedUser) |
38 |
const isRequestCompleted = useSelector((state: RootState) => state.usersDetail.isRequestCompleted) |
|
37 | 39 |
const [selectedUserId, setSelectedUserId] = useState<number | undefined>(undefined) |
38 | 40 |
|
39 | 41 |
// Local state to display any error relevant error |
... | ... | |
59 | 61 |
// Use effect to read the error and consume it |
60 | 62 |
useEffect(() => { |
61 | 63 |
if (users.length > 0) { |
62 |
setSelectedUserId(0) |
|
63 |
dispatch(setSelectedUser(users.at(0))) |
|
64 |
console.log("selected id: " + selectedUser) |
|
64 |
// if there is user selected, leave that state - handled by useEffect on isRequestCompleted |
|
65 |
if (selectedUser !== undefined) { |
|
66 |
const selUser = users.find(u => u.email === selectedUser.email) |
|
67 |
if (selUser !== undefined) { |
|
68 |
console.log("Selected from before") |
|
69 |
console.log(selUser) |
|
70 |
setSelectedUserId(users.findIndex(u => u.email === selectedUser.email)) |
|
71 |
dispatch(setSelectedUser(selUser)) |
|
72 |
return |
|
73 |
} |
|
74 |
} else { |
|
75 |
setSelectedUserId(0) |
|
76 |
dispatch(setSelectedUser(users.at(0))) |
|
77 |
} |
|
65 | 78 |
} |
66 |
}, [users]) |
|
79 |
}, [users, dispatch]) |
|
80 |
|
|
81 |
useEffect(() => { |
|
82 |
if (isRequestCompleted) { |
|
83 |
dispatch(resetIsRequestCompleted()) |
|
84 |
// dispatch(clear()) |
|
85 |
dispatch(fetchUsers()) |
|
86 |
// setSelectedUserId(0) |
|
87 |
// dispatch(setSelectedUser(users.at(0))) |
|
88 |
// |
|
89 |
// if (users.length > 0) { |
|
90 |
// setSelectedUserId(0) |
|
91 |
// dispatch(setSelectedUser(users.at(0))) |
|
92 |
// console.log("selected id: " + selectedUser) |
|
93 |
// } |
|
94 |
} |
|
95 |
}, [isRequestCompleted, dispatch]) |
|
67 | 96 |
|
68 | 97 |
|
69 | 98 |
// Use effect to read the error and consume it |
... | ... | |
87 | 116 |
} |
88 | 117 |
}, [dispatch]) |
89 | 118 |
|
90 |
const childClosed = () => { |
|
91 |
dispatch(clear()) |
|
92 |
dispatch(fetchUsers()) |
|
93 |
setSelectedUserId(0) |
|
94 |
dispatch(setSelectedUser(users.at(0))) |
|
95 |
} |
|
96 |
|
|
97 | 119 |
return ( |
98 | 120 |
<Fragment> |
99 | 121 |
<ShowErrorIfPresent err={displayError} /> |
... | ... | |
129 | 151 |
</Grid> |
130 | 152 |
<Grid item md={9} xs={6}> |
131 | 153 |
{selectedUserId !== undefined ? |
132 |
<UserDetail user={selectedUser as UserDto} onClose={childClosed} />
|
|
154 |
<UserDetail user={selectedUser as UserDto}/> |
|
133 | 155 |
: null } |
134 | 156 |
</Grid> |
135 | 157 |
</Grid> |
frontend/src/features/Administration/UserDetail.tsx | ||
---|---|---|
5 | 5 |
Paper, |
6 | 6 |
Typography, |
7 | 7 |
FormGroup, |
8 |
FormControlLabel, Container
|
|
8 |
FormControlLabel |
|
9 | 9 |
} from '@mui/material' |
10 | 10 |
import React, {Fragment, useEffect, useState} from 'react' |
11 | 11 |
import axiosInstance from "../../api/api" |
... | ... | |
14 | 14 |
import ClearIcon from "@mui/icons-material/Clear" |
15 | 15 |
import CheckIcon from '@mui/icons-material/Check'; |
16 | 16 |
import {useDispatch, useSelector} from "react-redux" |
17 |
import {fetchUsers, savePermissions} from "./userDetailThunks"
|
|
17 |
import {deleteUser, savePermissions} from "./userDetailThunks"
|
|
18 | 18 |
import {RootState} from "../redux/store" |
19 | 19 |
import {setSelectedUserPermissions} from "./userDetailSlice" |
20 | 20 |
|
21 | 21 |
export interface UserDetailProps { |
22 |
user: UserDto, |
|
23 |
onClose: () => void |
|
22 |
user: UserDto |
|
24 | 23 |
} |
25 | 24 |
|
26 | 25 |
const UserDetail = (props: UserDetailProps) => { |
27 |
const selectedUser = useSelector((state: RootState) => state.usersDetail.selectedUser) |
|
26 |
const selectedUser: UserDto | undefined = useSelector((state: RootState) => state.usersDetail.selectedUser)
|
|
28 | 27 |
const [canRead, setCanRead] = useState(selectedUser?.permissions?.canRead) |
29 | 28 |
const [canWrite, setCanWrite] = useState(selectedUser?.permissions?.canWrite) |
30 | 29 |
const [canDelete, setCanDelete] = useState(selectedUser?.permissions?.canDelete) |
... | ... | |
69 | 68 |
} |
70 | 69 |
|
71 | 70 |
|
72 |
const deleteUser = async () => { |
|
73 |
const { data, status } = await axiosInstance.delete( |
|
74 |
`/users/${selectedUser?.email}` |
|
75 |
) |
|
76 |
if (status !== 200) { |
|
77 |
// TODO dodělat zpracování erroru |
|
78 |
return |
|
79 |
} |
|
80 |
props.onClose(); |
|
71 |
const prepareDeleteUser = async () => { |
|
72 |
dispatch(deleteUser()) |
|
81 | 73 |
} |
82 | 74 |
|
83 | 75 |
const prepareSavePermissions = () => { |
84 | 76 |
dispatch(setSelectedUserPermissions({canRead: canRead, canWrite: canWrite, canDelete: canDelete})) |
85 | 77 |
dispatch(savePermissions()) |
86 |
dispatch(fetchUsers()) |
|
87 | 78 |
} |
88 | 79 |
|
89 | 80 |
return ( |
... | ... | |
139 | 130 |
<Button startIcon={<ClearIcon />} |
140 | 131 |
variant="contained" |
141 | 132 |
color="primary" |
142 |
onClick={deleteUser}
|
|
133 |
onClick={prepareDeleteUser}
|
|
143 | 134 |
sx={{ m: 2 }} > |
144 | 135 |
Delete |
145 | 136 |
</Button> |
frontend/src/features/Administration/userDetailSlice.tsx | ||
---|---|---|
1 | 1 |
import { createSlice } from '@reduxjs/toolkit' |
2 | 2 |
import {PermissionDto, UserDto} from '../../swagger/data-contracts' |
3 |
import {fetchUsers} from './userDetailThunks'
|
|
3 |
import {deleteUser, fetchUsers, savePermissions} from './userDetailThunks'
|
|
4 | 4 |
import {number} from "yup" |
5 | 5 |
|
6 | 6 |
export interface UsersDetailState { |
7 | 7 |
users: UserDto[] // list of all fetched items |
8 | 8 |
loading: boolean // whether the users are loading |
9 |
error?: string,
|
|
10 |
selectedUser?: UserDto,
|
|
9 |
error?: string |
|
10 |
selectedUser?: UserDto |
|
11 | 11 |
permissions?: PermissionDto |
12 |
isRequestCompleted: boolean |
|
12 | 13 |
} |
13 | 14 |
|
14 | 15 |
const initialState: UsersDetailState = { |
... | ... | |
16 | 17 |
loading: true, |
17 | 18 |
error: undefined, |
18 | 19 |
selectedUser: undefined, |
19 |
permissions: undefined |
|
20 |
permissions: undefined, |
|
21 |
isRequestCompleted: false |
|
20 | 22 |
} |
21 | 23 |
|
22 | 24 |
const usersDetailSlice = createSlice({ |
... | ... | |
34 | 36 |
...state, |
35 | 37 |
permissions: action.payload |
36 | 38 |
}), |
39 |
resetIsRequestCompleted: (state) => ({ |
|
40 |
...state, |
|
41 |
isRequestCompleted: false |
|
42 |
}), |
|
37 | 43 |
}, |
38 | 44 |
extraReducers: (builder) => { |
39 | 45 |
builder.addCase(fetchUsers.pending, (state) => ({ |
... | ... | |
50 | 56 |
loading: false, |
51 | 57 |
error: action.error.message as string, |
52 | 58 |
})) |
59 |
builder.addCase(savePermissions.fulfilled, (state, action) => ({ |
|
60 |
...state, |
|
61 |
isRequestCompleted: true, |
|
62 |
})) |
|
63 |
builder.addCase(deleteUser.fulfilled, (state, action) => ({ |
|
64 |
...state, |
|
65 |
isRequestCompleted: true, |
|
66 |
})) |
|
53 | 67 |
}, |
54 | 68 |
}) |
55 | 69 |
|
... | ... | |
59 | 73 |
consumeError, |
60 | 74 |
setSelectedUser, |
61 | 75 |
setSelectedUserPermissions, |
76 |
resetIsRequestCompleted |
|
62 | 77 |
} = usersDetailSlice.actions |
63 | 78 |
const usersDetailReducer = usersDetailSlice.reducer |
64 | 79 |
export default usersDetailReducer |
frontend/src/features/Administration/userDetailThunks.tsx | ||
---|---|---|
59 | 59 |
} |
60 | 60 |
} |
61 | 61 |
) |
62 |
|
|
63 |
export const deleteUser = createAsyncThunk( |
|
64 |
'usersDetail/deleteUser', |
|
65 |
async (dispatch, { getState }) => { |
|
66 |
const { usersDetail } = getState() as { usersDetail: UsersDetailState } |
|
67 |
console.log("savePermissions called") |
|
68 |
|
|
69 |
const selectedUser = usersDetail.selectedUser |
|
70 |
|
|
71 |
if (selectedUser === undefined) { |
|
72 |
return Promise.reject("User is undefined") |
|
73 |
} |
|
74 |
const { data, status } = await axiosInstance.delete( |
|
75 |
`/users/${selectedUser?.email}` |
|
76 |
) |
|
77 |
if (status !== 200) { |
|
78 |
// TODO dodělat zpracování erroru |
|
79 |
return Promise.reject("Deleting user failed") |
|
80 |
} |
|
81 |
return status |
|
82 |
} |
|
83 |
) |
Také k dispozici: Unified diff
Administation page finished
re #9818