@gnar-engine/cli 1.0.4 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bootstrap/deploy.localdev.yml +30 -3
- package/bootstrap/secrets.localdev.yml +15 -4
- package/bootstrap/services/control/src/config.js +4 -0
- package/bootstrap/services/page/Dockerfile +23 -0
- package/bootstrap/services/page/package.json +16 -0
- package/bootstrap/services/page/src/app.js +50 -0
- package/bootstrap/services/page/src/commands/block.handler.js +94 -0
- package/bootstrap/services/page/src/commands/page.handler.js +167 -0
- package/bootstrap/services/page/src/config.js +62 -0
- package/bootstrap/services/page/src/controllers/block.http.controller.js +87 -0
- package/bootstrap/services/page/src/controllers/message.controller.js +51 -0
- package/bootstrap/services/page/src/controllers/page.http.controller.js +89 -0
- package/bootstrap/services/page/src/policies/block.policy.js +50 -0
- package/bootstrap/services/page/src/policies/page.policy.js +49 -0
- package/bootstrap/services/page/src/schema/page.schema.js +139 -0
- package/bootstrap/services/page/src/services/block.service.js +83 -0
- package/bootstrap/services/page/src/services/page.service.js +83 -0
- package/bootstrap/services/portal/Dockerfile +20 -0
- package/bootstrap/services/portal/README.md +73 -0
- package/bootstrap/services/portal/index.html +13 -0
- package/bootstrap/services/portal/nginx.conf +5 -0
- package/bootstrap/services/portal/package.json +33 -0
- package/bootstrap/services/portal/public/vite.svg +1 -0
- package/bootstrap/services/portal/react-router.config.js +7 -0
- package/bootstrap/services/portal/src/App.jsx +16 -0
- package/bootstrap/services/portal/src/assets/gnar-engine-white-logo.svg +9 -0
- package/bootstrap/services/portal/src/assets/icon-agent.svg +6 -0
- package/bootstrap/services/portal/src/assets/icon-cog.svg +4 -0
- package/bootstrap/services/portal/src/assets/icon-delete.svg +3 -0
- package/bootstrap/services/portal/src/assets/icon-home.svg +3 -0
- package/bootstrap/services/portal/src/assets/icon-padlock.svg +3 -0
- package/bootstrap/services/portal/src/assets/icon-page.svg +6 -0
- package/bootstrap/services/portal/src/assets/icon-reports.svg +3 -0
- package/bootstrap/services/portal/src/assets/icon-user.svg +3 -0
- package/bootstrap/services/portal/src/assets/icon-users.svg +3 -0
- package/bootstrap/services/portal/src/assets/login-green-rad-back-1.jpg +0 -0
- package/bootstrap/services/portal/src/assets/react.svg +1 -0
- package/bootstrap/services/portal/src/components/CrudList/CrudList.jsx +85 -0
- package/bootstrap/services/portal/src/components/CrudList/CrudList.less +59 -0
- package/bootstrap/services/portal/src/components/CustomSelect/CustomSelect.jsx +81 -0
- package/bootstrap/services/portal/src/components/CustomSelect/CustomSelect.less +0 -0
- package/bootstrap/services/portal/src/components/LoginForm/LoginForm.jsx +58 -0
- package/bootstrap/services/portal/src/components/PageBlockSwitch/PageBlockSwitch.jsx +129 -0
- package/bootstrap/services/portal/src/components/Sidebar/Sidebar.jsx +33 -0
- package/bootstrap/services/portal/src/components/Sidebar/Sidebar.less +37 -0
- package/bootstrap/services/portal/src/components/Topbar/Topbar.jsx +19 -0
- package/bootstrap/services/portal/src/components/Topbar/Topbar.less +22 -0
- package/bootstrap/services/portal/src/components/UserInfo/UserInfo.jsx +33 -0
- package/bootstrap/services/portal/src/components/UserInfo/UserInfo.less +21 -0
- package/bootstrap/services/portal/src/css/style.css +711 -0
- package/bootstrap/services/portal/src/data/pages.data.js +10 -0
- package/bootstrap/services/portal/src/elements/CustomSelect/CustomSelect.jsx +65 -0
- package/bootstrap/services/portal/src/elements/CustomSelect/CustomSelect.less +102 -0
- package/bootstrap/services/portal/src/elements/ImageInput/ImageInput.jsx +115 -0
- package/bootstrap/services/portal/src/elements/ImageInput/ImageInput.less +43 -0
- package/bootstrap/services/portal/src/elements/ImageMultiInput/ImageMultiInput.jsx +124 -0
- package/bootstrap/services/portal/src/elements/ImageMultiInput/ImageMultiInput.less +0 -0
- package/bootstrap/services/portal/src/elements/Repeater/Repeater.jsx +52 -0
- package/bootstrap/services/portal/src/elements/Repeater/Repeater.less +70 -0
- package/bootstrap/services/portal/src/elements/RichTextInput/RichTextInput.jsx +18 -0
- package/bootstrap/services/portal/src/elements/RichTextInput/RichTextInput.less +37 -0
- package/bootstrap/services/portal/src/elements/SaveButton/SaveButton.jsx +45 -0
- package/bootstrap/services/portal/src/elements/SelectRepeater/SelectRepeater.jsx +63 -0
- package/bootstrap/services/portal/src/elements/SelectRepeater/SelectRepeater.less +23 -0
- package/bootstrap/services/portal/src/elements/TextInput/TextInput.jsx +17 -0
- package/bootstrap/services/portal/src/layouts/Card/Card.jsx +15 -0
- package/bootstrap/services/portal/src/layouts/PortalLayout/PortalLayout.jsx +29 -0
- package/bootstrap/services/portal/src/layouts/PortalLayout/PortalLayout.less +49 -0
- package/bootstrap/services/portal/src/main.jsx +51 -0
- package/bootstrap/services/portal/src/pages/BlockSinglePage/BlockSinglePage.jsx +277 -0
- package/bootstrap/services/portal/src/pages/BlocksPage/BlocksPage.jsx +23 -0
- package/bootstrap/services/portal/src/pages/DashboardPage/DashboardPage.jsx +11 -0
- package/bootstrap/services/portal/src/pages/DashboardPage/DashboardPage.less +0 -0
- package/bootstrap/services/portal/src/pages/LoginPage/LoginPage.jsx +21 -0
- package/bootstrap/services/portal/src/pages/LoginPage/LoginPage.less +51 -0
- package/bootstrap/services/portal/src/pages/PageSinglePage/PageSinglePage.jsx +338 -0
- package/bootstrap/services/portal/src/pages/PagesPage/PagesPage.jsx +23 -0
- package/bootstrap/services/portal/src/pages/UserSinglePage/UserSinglePage.jsx +9 -0
- package/bootstrap/services/portal/src/pages/UserSinglePage/UserSinglePage.less +0 -0
- package/bootstrap/services/portal/src/pages/UsersPage/UsersPage.jsx +25 -0
- package/bootstrap/services/portal/src/pages/UsersPage/UsersPage.less +0 -0
- package/bootstrap/services/portal/src/services/block.js +28 -0
- package/bootstrap/services/portal/src/services/client.js +67 -0
- package/bootstrap/services/portal/src/services/gravatar.js +14 -0
- package/bootstrap/services/portal/src/services/page.js +28 -0
- package/bootstrap/services/portal/src/services/storage.js +62 -0
- package/bootstrap/services/portal/src/services/user.js +41 -0
- package/bootstrap/services/portal/src/slices/authSlice.js +101 -0
- package/bootstrap/services/portal/src/store/configureStore.js +10 -0
- package/bootstrap/services/portal/src/style/cards.less +57 -0
- package/bootstrap/services/portal/src/style/global.less +204 -0
- package/bootstrap/services/portal/src/style/icons.less +21 -0
- package/bootstrap/services/portal/src/style/inputs.less +52 -0
- package/bootstrap/services/portal/src/style/main.less +28 -0
- package/bootstrap/services/portal/src/utils/utils.js +9 -0
- package/bootstrap/services/portal/vite.config.js +12 -0
- package/bootstrap/services/user/src/app.js +6 -1
- package/bootstrap/services/user/src/commands/user.handler.js +0 -3
- package/bootstrap/services/user/src/config.js +5 -1
- package/bootstrap/services/user/src/policies/user.policy.js +3 -1
- package/bootstrap/services/user/src/tests/commands/user.test.js +22 -0
- package/install-from-clone.sh +30 -0
- package/package.json +1 -1
- package/src/cli.js +8 -0
- package/src/dev/commands.js +10 -2
- package/src/dev/dev.service.js +147 -60
- package/src/provisioner/Dockerfile +27 -0
- package/src/provisioner/package.json +19 -0
- package/src/provisioner/src/app.js +56 -0
- package/src/provisioner/src/services/mongodb.js +58 -0
- package/src/provisioner/src/services/mysql.js +51 -0
- package/src/provisioner/src/services/secrets.js +84 -0
- package/src/scaffolder/commands.js +1 -1
- package/src/scaffolder/scaffolder.handler.js +40 -15
- package/templates/service/src/app.js.hbs +12 -1
- package/templates/service/src/commands/{{serviceName}}.handler.js.hbs +1 -1
- package/templates/service/src/mongodb.config.js.hbs +5 -1
- package/templates/service/src/mysql.config.js.hbs +4 -0
- package/bootstrap/services/user/src/tests/user.test.js +0 -126
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { createSlice, createAsyncThunk, createAction } from '@reduxjs/toolkit';
|
|
2
|
+
import { getAuthToken, getAuthUser, setAuthToken, setAuthUser, removeAuthToken, removeAuthUser } from '../services/storage.js';
|
|
3
|
+
import { user } from '../services/user.js';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export const login = createAsyncThunk('auth/login', async ({ username, password }) => {
|
|
7
|
+
let response;
|
|
8
|
+
try {
|
|
9
|
+
response = await user.authenticate({ username, password });
|
|
10
|
+
} catch (error) {
|
|
11
|
+
response = error.response;
|
|
12
|
+
}
|
|
13
|
+
return response;
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
export const register = createAsyncThunk('auth/register', async (user) => {
|
|
17
|
+
let response;
|
|
18
|
+
try {
|
|
19
|
+
response = await user.createUser(user);
|
|
20
|
+
} catch (error) {
|
|
21
|
+
response = error.response;
|
|
22
|
+
}
|
|
23
|
+
return response;
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
export const logout = createAction('auth/logout');
|
|
27
|
+
|
|
28
|
+
export const authSlice = createSlice({
|
|
29
|
+
name: 'auth',
|
|
30
|
+
initialState: {
|
|
31
|
+
authUser: getAuthUser() ? JSON.parse(getAuthUser()) : null,
|
|
32
|
+
accessToken: getAuthToken() ? getAuthToken() : '',
|
|
33
|
+
authLoading: false,
|
|
34
|
+
authError: ''
|
|
35
|
+
},
|
|
36
|
+
reducers: {
|
|
37
|
+
},
|
|
38
|
+
extraReducers: builder => {
|
|
39
|
+
builder
|
|
40
|
+
.addCase(login.pending, (state, action) => {
|
|
41
|
+
state.authLoading = true;
|
|
42
|
+
state.authError = '';
|
|
43
|
+
})
|
|
44
|
+
.addCase(login.fulfilled, (state, action) => {
|
|
45
|
+
console.log('login.fulfilled', action.payload);
|
|
46
|
+
state.authLoading = false;
|
|
47
|
+
state.authError = action.payload.message ? action.payload.message : '';
|
|
48
|
+
|
|
49
|
+
if (action.payload.token) {
|
|
50
|
+
state.accessToken = action.payload.token;
|
|
51
|
+
state.authUser = action.payload.user;
|
|
52
|
+
|
|
53
|
+
// store in local storage
|
|
54
|
+
setAuthToken(action.payload.token);
|
|
55
|
+
setAuthUser(JSON.stringify(action.payload.user));
|
|
56
|
+
|
|
57
|
+
// redirect to portal
|
|
58
|
+
window.location.href= '/portal/dashboard';
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
.addCase(logout, (state, action) => {
|
|
62
|
+
// Clear auth state
|
|
63
|
+
state.authUser = '';
|
|
64
|
+
state.accessToken = '';
|
|
65
|
+
|
|
66
|
+
// Remove from local storage
|
|
67
|
+
removeAuthToken();
|
|
68
|
+
removeAuthUser();
|
|
69
|
+
|
|
70
|
+
// Redirect to login page
|
|
71
|
+
window.location.href = '/portal/login';
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
// Register
|
|
75
|
+
.addCase(register.pending, (state, action) => {
|
|
76
|
+
state.status = 'loading';
|
|
77
|
+
})
|
|
78
|
+
.addCase(register.fulfilled, (state, action) => {
|
|
79
|
+
state.status = 'idle';
|
|
80
|
+
|
|
81
|
+
if (action.payload.users && action.payload.users.length > 0) {
|
|
82
|
+
const user = action.payload.users[0];
|
|
83
|
+
|
|
84
|
+
state.logged_in = true;
|
|
85
|
+
state.user = user;
|
|
86
|
+
|
|
87
|
+
// Save user auth details (if needed)
|
|
88
|
+
setAuthUser(JSON.stringify(user));
|
|
89
|
+
|
|
90
|
+
// Redirect to dashboard page
|
|
91
|
+
window.location.href = '/portal/dashboard';
|
|
92
|
+
} else {
|
|
93
|
+
state.logged_in = false;
|
|
94
|
+
state.user = {};
|
|
95
|
+
state.error = 'Registration failed: Invalid response';
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
export default authSlice;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
.card {
|
|
2
|
+
border: 1px solid #3C3C3C;
|
|
3
|
+
border-radius: 10px;
|
|
4
|
+
margin-bottom: 30px;
|
|
5
|
+
width: 100%;
|
|
6
|
+
|
|
7
|
+
.card-title {
|
|
8
|
+
background: #242424;
|
|
9
|
+
margin: 0px;
|
|
10
|
+
padding: 20px 40px;
|
|
11
|
+
font-size: 16px;
|
|
12
|
+
font-weight: 400;
|
|
13
|
+
border-top-left-radius: 10px;
|
|
14
|
+
border-top-right-radius: 10px;
|
|
15
|
+
overflow: hidden;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.card-content {
|
|
19
|
+
padding: 30px 40px;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.instruction {
|
|
23
|
+
color: @mid-grey;
|
|
24
|
+
margin-bottom: 30px;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.card-columns {
|
|
29
|
+
display: flex;
|
|
30
|
+
flex-wrap: no-wrap;
|
|
31
|
+
gap: 50px;
|
|
32
|
+
|
|
33
|
+
& > .col-66 {
|
|
34
|
+
width: 66%;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
& > .col-33 {
|
|
38
|
+
width: 33%;
|
|
39
|
+
}
|
|
40
|
+
p {
|
|
41
|
+
margin-top: 0px;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.card {
|
|
46
|
+
input[type="text"],
|
|
47
|
+
input[type="password"],
|
|
48
|
+
input[type="email"],
|
|
49
|
+
textarea {
|
|
50
|
+
margin-top: 8px;
|
|
51
|
+
background: @dark-3;
|
|
52
|
+
|
|
53
|
+
&::placeholder {
|
|
54
|
+
color: @light-grey;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
|
|
2
|
+
/* vars */
|
|
3
|
+
@dark-1: #242424;
|
|
4
|
+
@dark-2: #2C2C2C;
|
|
5
|
+
@dark-3: #242424;
|
|
6
|
+
@dark-grey: #3C3C3C;
|
|
7
|
+
@mid-grey: #868686;
|
|
8
|
+
@light-grey: #D9D9D9;
|
|
9
|
+
@white: #ffffff;
|
|
10
|
+
@green-main: #47DCA6;
|
|
11
|
+
@error-red: #FF4D4F;
|
|
12
|
+
|
|
13
|
+
@content-width: 1550px;
|
|
14
|
+
|
|
15
|
+
/* Fonts */
|
|
16
|
+
// @import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
|
|
17
|
+
|
|
18
|
+
html {
|
|
19
|
+
background: @dark-1;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
body,
|
|
23
|
+
body * {
|
|
24
|
+
font-family: ubuntu, sans-serif !important;
|
|
25
|
+
color: @white;
|
|
26
|
+
font-size: 14px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Global styles */
|
|
30
|
+
|
|
31
|
+
h1 {
|
|
32
|
+
margin-top: 0px;
|
|
33
|
+
margin-bottom: 40px;
|
|
34
|
+
font-size: 18px;
|
|
35
|
+
font-weight: 400;
|
|
36
|
+
}
|
|
37
|
+
h2 {
|
|
38
|
+
font-size: 14px;
|
|
39
|
+
font-weight: 500;
|
|
40
|
+
}
|
|
41
|
+
body {
|
|
42
|
+
margin: 0px;
|
|
43
|
+
|
|
44
|
+
&, #root {
|
|
45
|
+
min-height: 100vh;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.separator {
|
|
50
|
+
display: inline-block;
|
|
51
|
+
margin-top: 20px;
|
|
52
|
+
margin-bottom: 20px;
|
|
53
|
+
height: 0px;
|
|
54
|
+
border-top: 1px solid @dark-grey;
|
|
55
|
+
width: 100%;
|
|
56
|
+
}
|
|
57
|
+
.content-wrap {
|
|
58
|
+
display: flex;
|
|
59
|
+
justify-content: center;
|
|
60
|
+
}
|
|
61
|
+
.content {
|
|
62
|
+
width: 100%;
|
|
63
|
+
max-width: @content-width;
|
|
64
|
+
margin-left: auto;
|
|
65
|
+
margin-right: auto;
|
|
66
|
+
padding-left: 20px;
|
|
67
|
+
padding-right: 20px;
|
|
68
|
+
}
|
|
69
|
+
.instruction {
|
|
70
|
+
margin-top: 10px;
|
|
71
|
+
margin-bottom: 10px;
|
|
72
|
+
color: @light-grey;
|
|
73
|
+
}
|
|
74
|
+
.flex-row {
|
|
75
|
+
display: flex;
|
|
76
|
+
flex-direction: row;
|
|
77
|
+
justify-content: space-between;
|
|
78
|
+
gap: 20px;
|
|
79
|
+
}
|
|
80
|
+
.flex-row-end{
|
|
81
|
+
align-items: flex-end;
|
|
82
|
+
}
|
|
83
|
+
.form-cont {
|
|
84
|
+
width: 100%;
|
|
85
|
+
margin-bottom: 30px;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.full-width-select-cont,
|
|
89
|
+
.full-width-select-cont .custom-select {
|
|
90
|
+
min-width: 100%;
|
|
91
|
+
}
|
|
92
|
+
.flex-row-select-cont {
|
|
93
|
+
min-width: 100%;
|
|
94
|
+
display: flex;
|
|
95
|
+
flex-direction: row;
|
|
96
|
+
justify-content: space-between;
|
|
97
|
+
gap: 20px;
|
|
98
|
+
|
|
99
|
+
.custom-select {
|
|
100
|
+
width: 100%;
|
|
101
|
+
max-width: unset;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.flex-row-checkbox-cont {
|
|
106
|
+
min-width: 100%;
|
|
107
|
+
display: flex;
|
|
108
|
+
flex-direction: row;
|
|
109
|
+
justify-content: space-between;
|
|
110
|
+
gap: 20px;
|
|
111
|
+
|
|
112
|
+
label {
|
|
113
|
+
display: inline-block;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.flex-row-buttons-cont {
|
|
118
|
+
display: flex;
|
|
119
|
+
flex-direction: row;
|
|
120
|
+
justify-content: flex-end;
|
|
121
|
+
gap: 20px;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.button {
|
|
125
|
+
&.button-loading {
|
|
126
|
+
background: @dark-grey !important;
|
|
127
|
+
cursor: not-allowed !important;
|
|
128
|
+
|
|
129
|
+
&:disabled {
|
|
130
|
+
cursor: not-allowed;
|
|
131
|
+
opacity: 0.5;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.text-link {
|
|
137
|
+
color: @light-grey;
|
|
138
|
+
cursor: pointer;
|
|
139
|
+
display: inline-block;
|
|
140
|
+
text-decoration: none;
|
|
141
|
+
font-size: 14px;
|
|
142
|
+
|
|
143
|
+
&:hover {
|
|
144
|
+
opacity: 0.8;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.input {
|
|
149
|
+
font-size: 12px;
|
|
150
|
+
color: @dark-2;
|
|
151
|
+
|
|
152
|
+
input {
|
|
153
|
+
background: @dark-1 !important;
|
|
154
|
+
color: white;
|
|
155
|
+
padding: 10px;
|
|
156
|
+
padding-left: 48px;
|
|
157
|
+
border-radius: 20px;
|
|
158
|
+
font-size: 14px;
|
|
159
|
+
margin-top: 5px;
|
|
160
|
+
margin-bottom: 5px !important;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
&.icon {
|
|
164
|
+
background-size: 15px;
|
|
165
|
+
background-repeat: no-repeat;
|
|
166
|
+
background-position: 15px 15px;
|
|
167
|
+
|
|
168
|
+
input {
|
|
169
|
+
padding-left: 50px;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.single-crud-page {
|
|
175
|
+
.top-bar {
|
|
176
|
+
margin-bottom: 20px;
|
|
177
|
+
}
|
|
178
|
+
.bottom-bar {
|
|
179
|
+
margin-top: 20px;
|
|
180
|
+
margin-bottom: 20px;
|
|
181
|
+
justify-content: flex-end;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.error-messages {
|
|
186
|
+
display: flex;
|
|
187
|
+
flex-direction: row;
|
|
188
|
+
justify-content: flex-end;
|
|
189
|
+
|
|
190
|
+
ul {
|
|
191
|
+
list-style: none;
|
|
192
|
+
margin: 0px;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
li, a, span, p {
|
|
196
|
+
color: @error-red;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.button-group {
|
|
201
|
+
button {
|
|
202
|
+
margin-left: 15px;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
.icon-dashboard {
|
|
2
|
+
background: url('../assets/icon-home.svg');
|
|
3
|
+
}
|
|
4
|
+
.icon-users {
|
|
5
|
+
background: url('../assets/icon-users.svg');
|
|
6
|
+
}
|
|
7
|
+
.icon-settings {
|
|
8
|
+
background: url('../assets/icon-settings.svg');
|
|
9
|
+
}
|
|
10
|
+
.icon-reports {
|
|
11
|
+
background: url('../assets/icon-reports.svg');
|
|
12
|
+
}
|
|
13
|
+
.icon-agent {
|
|
14
|
+
background: url('../assets/icon-agent.svg');
|
|
15
|
+
}
|
|
16
|
+
.icon-page {
|
|
17
|
+
background: url('../assets/icon-page.svg');
|
|
18
|
+
}
|
|
19
|
+
.icon-delete {
|
|
20
|
+
background: url('../assets/icon-delete.svg');
|
|
21
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
input[type="text"],
|
|
2
|
+
input[type="password"],
|
|
3
|
+
input[type="email"],
|
|
4
|
+
textarea,
|
|
5
|
+
.custom-select .custom-select-input {
|
|
6
|
+
border: 1px solid @dark-grey;
|
|
7
|
+
border-radius: 17px;
|
|
8
|
+
padding: 8px 10px;
|
|
9
|
+
font-size: 14px;
|
|
10
|
+
width: 100%;
|
|
11
|
+
box-sizing: border-box;
|
|
12
|
+
background: none;
|
|
13
|
+
margin-bottom: 25px;
|
|
14
|
+
transition: border-color 0.3s;
|
|
15
|
+
|
|
16
|
+
&::placeholder {
|
|
17
|
+
color: @white;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
&:focus {
|
|
21
|
+
outline: none;
|
|
22
|
+
border-color: @green-main;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
button {
|
|
27
|
+
background: @green-main;
|
|
28
|
+
border: 1px solid @green-main;
|
|
29
|
+
border-radius: 17px;
|
|
30
|
+
color: @dark-1;
|
|
31
|
+
padding: 9px 19px;
|
|
32
|
+
min-width: 143px;
|
|
33
|
+
font-weight: 700;
|
|
34
|
+
transition: opacity 0.3s;
|
|
35
|
+
|
|
36
|
+
&[type="submit"] {
|
|
37
|
+
margin-top: 25px;
|
|
38
|
+
margin-bottom: 25px;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
&:hover {
|
|
42
|
+
opacity: 0.8;
|
|
43
|
+
cursor: pointer;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
&.secondary-btn {
|
|
47
|
+
background: @dark-2;
|
|
48
|
+
color: @white;
|
|
49
|
+
border: 1px solid @dark-grey;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// GLOBAL
|
|
2
|
+
@import "global.less";
|
|
3
|
+
@import "inputs.less";
|
|
4
|
+
@import "icons.less";
|
|
5
|
+
@import "cards.less";
|
|
6
|
+
|
|
7
|
+
// LAYOUTS
|
|
8
|
+
@import "../layouts/PortalLayout/PortalLayout.less";
|
|
9
|
+
|
|
10
|
+
// COMPONENTS
|
|
11
|
+
@import "../components/Sidebar/Sidebar.less";
|
|
12
|
+
@import "../components/Topbar/Topbar.less";
|
|
13
|
+
@import "../components/UserInfo/UserInfo.less";
|
|
14
|
+
@import "../components/CrudList/CrudList.less";
|
|
15
|
+
|
|
16
|
+
// ELEMENTS
|
|
17
|
+
@import "../elements/Repeater/Repeater.less";
|
|
18
|
+
@import "../elements/CustomSelect/CustomSelect.less";
|
|
19
|
+
@import "../elements/RichTextInput/RichTextInput.less";
|
|
20
|
+
@import "../elements/ImageInput/ImageInput.less";
|
|
21
|
+
@import "../elements/ImageMultiInput/ImageMultiInput.less";
|
|
22
|
+
|
|
23
|
+
// PAGES
|
|
24
|
+
@import "../pages/LoginPage/LoginPage.less";
|
|
25
|
+
|
|
26
|
+
#MagiCSS-bookmarklet span {
|
|
27
|
+
color: black !important;
|
|
28
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
|
|
2
|
+
export const fileToBase64 = (file) => {
|
|
3
|
+
return new Promise((resolve, reject) => {
|
|
4
|
+
const reader = new FileReader();
|
|
5
|
+
reader.onload = () => resolve(reader.result); // returns "data:image/png;base64,..."
|
|
6
|
+
reader.onerror = reject;
|
|
7
|
+
reader.readAsDataURL(file);
|
|
8
|
+
});
|
|
9
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { defineConfig } from 'vite'
|
|
2
|
+
import react from '@vitejs/plugin-react'
|
|
3
|
+
|
|
4
|
+
// https://vite.dev/config/
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [react()],
|
|
7
|
+
server: {
|
|
8
|
+
host: true, // binds to 0.0.0.0
|
|
9
|
+
allowedHosts: ['.localhost', 'portal-service'], // allow extra hosts
|
|
10
|
+
},
|
|
11
|
+
base: '/portal/',
|
|
12
|
+
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { message, http, logger, db, registerService, webSockets } from '@gnar-engine/core';
|
|
1
|
+
import { message, http, logger, db, registerService, webSockets, test } from '@gnar-engine/core';
|
|
2
2
|
import { config } from './config.js';
|
|
3
3
|
import { messageHandlers } from './controllers/message.controller.js';
|
|
4
4
|
import { httpController as userPlatformHttpController } from './controllers/http.controller.js';
|
|
@@ -40,6 +40,11 @@ export const initService = async () => {
|
|
|
40
40
|
await registerService();
|
|
41
41
|
|
|
42
42
|
logger.info('G n a r E n g i n e | User Service initialised successfully.');
|
|
43
|
+
|
|
44
|
+
// Tests
|
|
45
|
+
if (config.environment === 'test' && config.runTests) {
|
|
46
|
+
test.runCommandTests({config});
|
|
47
|
+
}
|
|
43
48
|
}
|
|
44
49
|
|
|
45
50
|
initService();
|
|
@@ -76,7 +76,6 @@ commands.register('userService.getAuthenticatedUser', async ({token}) => {
|
|
|
76
76
|
const user_id = await auth.getAuthenticatedUser(token);
|
|
77
77
|
|
|
78
78
|
if (user_id) {
|
|
79
|
-
logger.info('Auth user: ' + user_id);
|
|
80
79
|
const userObj = await user.getById({id: user_id});
|
|
81
80
|
|
|
82
81
|
if (userObj) {
|
|
@@ -86,8 +85,6 @@ commands.register('userService.getAuthenticatedUser', async ({token}) => {
|
|
|
86
85
|
return userObj;
|
|
87
86
|
}
|
|
88
87
|
}
|
|
89
|
-
|
|
90
|
-
return;
|
|
91
88
|
});
|
|
92
89
|
|
|
93
90
|
|
|
@@ -6,12 +6,16 @@ export const config = {
|
|
|
6
6
|
// service name
|
|
7
7
|
serviceName: 'userService',
|
|
8
8
|
|
|
9
|
+
// environment
|
|
10
|
+
environment: process.env.USER_NODE_ENV || 'dev',
|
|
11
|
+
runTests: process.env.USER_RUN_TESTS || false,
|
|
12
|
+
|
|
9
13
|
// microservice | modular-monolith
|
|
10
14
|
architecture: process.env.GLOBAL_ARCHITECTURE || 'microservice',
|
|
11
15
|
|
|
12
16
|
// web server
|
|
13
17
|
http: {
|
|
14
|
-
allowedOrigins: [],
|
|
18
|
+
allowedOrigins: ['localhost', 'localhost:4003'],
|
|
15
19
|
allowedMethods: ['GET', 'POST', 'PUT', 'DELETE'],
|
|
16
20
|
allowedHeaders: ['Content-Type', 'Authorization'],
|
|
17
21
|
rateLimiting: {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { config } from '../config.js';
|
|
2
|
+
import { logger } from '@gnar-engine/core';
|
|
2
3
|
|
|
3
4
|
|
|
4
5
|
export const authorise = {
|
|
@@ -23,6 +24,7 @@ export const authorise = {
|
|
|
23
24
|
* Authorise get many users
|
|
24
25
|
*/
|
|
25
26
|
getMany: async (request, reply) => {
|
|
27
|
+
logger.info('user -' + JSON.stringify(request.user));
|
|
26
28
|
if (!request.user || request.user.role !== 'service_admin') {
|
|
27
29
|
reply.code(403).send({error: 'not authorised'});
|
|
28
30
|
}
|
|
@@ -78,4 +80,4 @@ export const authorise = {
|
|
|
78
80
|
reply.code(403).send({error: 'not authorised'});
|
|
79
81
|
}
|
|
80
82
|
}
|
|
81
|
-
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { commands, logger, test } from '@gnar-engine/core';
|
|
2
|
+
|
|
3
|
+
// Preparation before tests run
|
|
4
|
+
test.prep(async () => {
|
|
5
|
+
if (process.env.NODE_ENV === 'production') {
|
|
6
|
+
throw new Error('Do not run tests in production mode!');
|
|
7
|
+
}
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
// Test create user command
|
|
11
|
+
test.run('Create User Command', async () => {
|
|
12
|
+
const users = await commands.execute('createUsers', [
|
|
13
|
+
{
|
|
14
|
+
email: 'test@gnar.co.uk'
|
|
15
|
+
}
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
test.assert(users.length === 1, 'User was not created successfully');
|
|
19
|
+
test.assert(users[0].email === 'test@gnar.co.uk', 'User email does not match');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
TARGET_DIR="$HOME/.gnarengine"
|
|
5
|
+
CLI_DIR="$(pwd)"
|
|
6
|
+
|
|
7
|
+
echo "Installing Gnar Engine CLI from local source for user $USER..."
|
|
8
|
+
|
|
9
|
+
mkdir -p "$TARGET_DIR"
|
|
10
|
+
|
|
11
|
+
# Install dependencies
|
|
12
|
+
cd "$CLI_DIR"
|
|
13
|
+
npm install
|
|
14
|
+
|
|
15
|
+
# Link CLI to custom global folder
|
|
16
|
+
npm link --prefix "$TARGET_DIR"
|
|
17
|
+
|
|
18
|
+
# Bin path for npm link
|
|
19
|
+
BIN_PATH="$TARGET_DIR/bin"
|
|
20
|
+
|
|
21
|
+
# Add to shell PATH if not already present
|
|
22
|
+
for SHELLRC in "$HOME/.bashrc" "$HOME/.zshrc"; do
|
|
23
|
+
[ -f "$SHELLRC" ] || continue
|
|
24
|
+
if ! grep -q "$BIN_PATH" "$SHELLRC"; then
|
|
25
|
+
echo "export PATH=\"$BIN_PATH:\$PATH\"" >>"$SHELLRC"
|
|
26
|
+
echo "Added $BIN_PATH to PATH in $SHELLRC"
|
|
27
|
+
fi
|
|
28
|
+
done
|
|
29
|
+
|
|
30
|
+
echo "Gnar Engine CLI installed! Restart your terminal and run 'gnar --help'"
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -6,6 +6,7 @@ import { registerDevCommands } from './dev/commands.js';
|
|
|
6
6
|
import { registerControlCommands } from './control/commands.js';
|
|
7
7
|
import { registerScaffolderCommands } from './scaffolder/commands.js';
|
|
8
8
|
import { registerAgentCommands } from './agent/commands.js';
|
|
9
|
+
import path from 'path';
|
|
9
10
|
|
|
10
11
|
// Create a new program
|
|
11
12
|
const program = new Command();
|
|
@@ -28,3 +29,10 @@ G n a r E n g i n e - A powerful, AI ready microservice framework for modern a
|
|
|
28
29
|
|
|
29
30
|
// Parse CLI input
|
|
30
31
|
program.parse(process.argv);
|
|
32
|
+
|
|
33
|
+
// Consts
|
|
34
|
+
export const directories = {
|
|
35
|
+
scaffolderTemplates: path.join(import.meta.dirname, '../templates/service'),
|
|
36
|
+
bootstrap: path.join(import.meta.dirname, '../bootstrap'),
|
|
37
|
+
provisioner: path.join(import.meta.dirname, './provisioner')
|
|
38
|
+
}
|
package/src/dev/commands.js
CHANGED
|
@@ -12,6 +12,8 @@ export function registerDevCommands(program) {
|
|
|
12
12
|
.description('🛠️ Up Development Containers')
|
|
13
13
|
.option('-b, --build', 'Ruild without cache')
|
|
14
14
|
.option('-d, --detach', 'Run containers in background')
|
|
15
|
+
.option('-t --test', 'Run the tests with ephemeral databases')
|
|
16
|
+
.option('--test-service <service>', 'Run the tests for the specified service with ephemeral databases')
|
|
15
17
|
.addOption(new Option('--core-dev').hideHelp())
|
|
16
18
|
.action(async (options) => {
|
|
17
19
|
let response = {};
|
|
@@ -27,13 +29,19 @@ export function registerDevCommands(program) {
|
|
|
27
29
|
// Change to the active profile directory
|
|
28
30
|
const projectDir = activeProfile.PROJECT_DIR;
|
|
29
31
|
|
|
32
|
+
if (options.testService) {
|
|
33
|
+
options.test = true;
|
|
34
|
+
}
|
|
35
|
+
|
|
30
36
|
try {
|
|
31
37
|
up({
|
|
32
38
|
projectDir: projectDir,
|
|
33
39
|
build: options.build || false,
|
|
34
40
|
detach: options.detach || false,
|
|
35
|
-
coreDev: options.coreDev || false
|
|
36
|
-
|
|
41
|
+
coreDev: options.coreDev || false,
|
|
42
|
+
test: options.test || false,
|
|
43
|
+
testService: options.testService || ''
|
|
44
|
+
});
|
|
37
45
|
} catch (err) {
|
|
38
46
|
console.error("❌ Error running containers:", err.message);
|
|
39
47
|
process.exit(1);
|