zrb 1.0.0b10__py3-none-any.whl → 1.2.0__py3-none-any.whl
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.
- zrb/builtin/git.py +8 -8
- zrb/builtin/llm/llm_chat.py +3 -3
- zrb/builtin/project/add/fastapp/fastapp_input.py +1 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/column/add_column_task.py +99 -55
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/column/add_column_util.py +301 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_task.py +24 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_util.py +61 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/module/gateway/view/content/my-module/my-entity.html +297 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/gateway_subroute.py +24 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/navigation_config_file.py +8 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/input.py +3 -3
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_task.py +8 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_util.py +40 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/gateway/subroute/my_module.py +2 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/navigation_config_file.py +6 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/util/parser.py +2 -2
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/util/view.py +1 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/config.py +18 -8
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/config/navigation.py +39 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/route.py +52 -11
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/schema/navigation.py +95 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/subroute/auth.py +91 -8
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/util/auth.py +9 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/util/view.py +33 -8
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/auth/permission.html +311 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/auth/role.html +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/auth/user.html +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/error.html +4 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/login.html +67 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/logout.html +49 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/common/util.js +160 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/crud/style.css +14 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/crud/util.js +94 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/default/pico-style.css +23 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/default/script.js +44 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/default/style.css +102 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/template/default.html +73 -18
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/requirements.txt +1 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/test_homepage.py +2 -4
- zrb/builtin/project/create/project_task.py +2 -2
- zrb/builtin/random.py +3 -3
- zrb/builtin/setup/common_input.py +5 -5
- zrb/builtin/setup/tmux/tmux.py +1 -1
- zrb/builtin/setup/zsh/zsh.py +1 -1
- zrb/builtin/todo.py +4 -4
- zrb/input/base_input.py +17 -12
- zrb/input/bool_input.py +12 -5
- zrb/input/float_input.py +12 -5
- zrb/input/int_input.py +12 -5
- zrb/input/option_input.py +5 -5
- zrb/input/password_input.py +5 -5
- zrb/input/text_input.py +4 -4
- zrb/runner/web_route/refresh_token_api_route.py +1 -1
- zrb/runner/web_route/static/refresh-token.template.js +9 -0
- zrb/runner/web_route/static/static_route.py +1 -1
- zrb/util/load.py +13 -7
- {zrb-1.0.0b10.dist-info → zrb-1.2.0.dist-info}/METADATA +2 -2
- {zrb-1.0.0b10.dist-info → zrb-1.2.0.dist-info}/RECORD +60 -44
- zrb/util/llm/tool.py +0 -87
- {zrb-1.0.0b10.dist-info → zrb-1.2.0.dist-info}/WHEEL +0 -0
- {zrb-1.0.0b10.dist-info → zrb-1.2.0.dist-info}/entry_points.txt +0 -0
@@ -8,7 +8,18 @@ from zrb.util.codemod.modify_class_parent import prepend_parent_class
|
|
8
8
|
from zrb.util.codemod.modify_function import append_code_to_function
|
9
9
|
from zrb.util.codemod.modify_module import prepend_code_to_module
|
10
10
|
from zrb.util.file import read_file, write_file
|
11
|
-
from zrb.util.string.conversion import
|
11
|
+
from zrb.util.string.conversion import (
|
12
|
+
to_human_case,
|
13
|
+
to_kebab_case,
|
14
|
+
to_pascal_case,
|
15
|
+
to_snake_case,
|
16
|
+
)
|
17
|
+
|
18
|
+
|
19
|
+
def is_gateway_navigation_config_file(ctx: AnyContext, file_path: str) -> bool:
|
20
|
+
return file_path == os.path.join(
|
21
|
+
APP_DIR, "module", "gateway", "config", "navigation.py"
|
22
|
+
)
|
12
23
|
|
13
24
|
|
14
25
|
def get_add_permission_migration_script(ctx: AnyContext) -> str:
|
@@ -156,6 +167,19 @@ def is_module_gateway_subroute_file(ctx: AnyContext, file_path: str) -> bool:
|
|
156
167
|
return file_path == module_gateway_subroute_file
|
157
168
|
|
158
169
|
|
170
|
+
def is_module_gateway_subroute_view_file(ctx: AnyContext, file_path: str) -> bool:
|
171
|
+
module_gateway_subroute_file = os.path.join(
|
172
|
+
APP_DIR,
|
173
|
+
"module",
|
174
|
+
"gateway",
|
175
|
+
"view",
|
176
|
+
"content",
|
177
|
+
f"{to_kebab_case(ctx.input.module)}",
|
178
|
+
f"{to_kebab_case(ctx.input.entity)}.html",
|
179
|
+
)
|
180
|
+
return file_path == module_gateway_subroute_file
|
181
|
+
|
182
|
+
|
159
183
|
def update_migration_metadata_file(ctx: AnyContext, migration_metadata_file_path: str):
|
160
184
|
app_name = os.path.basename(APP_DIR)
|
161
185
|
existing_migration_metadata_code = read_file(migration_metadata_file_path)
|
@@ -318,7 +342,9 @@ def update_route_file(ctx: AnyContext, route_file_path: str):
|
|
318
342
|
|
319
343
|
def update_gateway_subroute_file(ctx: AnyContext, module_gateway_subroute_path: str):
|
320
344
|
snake_module_name = to_snake_case(ctx.input.module)
|
345
|
+
kebab_module_name = to_kebab_case(ctx.input.module)
|
321
346
|
snake_entity_name = to_snake_case(ctx.input.entity)
|
347
|
+
kebab_entity_name = to_kebab_case(ctx.input.entity)
|
322
348
|
snake_plural_entity_name = to_snake_case(ctx.input.plural)
|
323
349
|
kebab_plural_entity_name = to_kebab_case(ctx.input.plural)
|
324
350
|
pascal_entity_name = to_pascal_case(ctx.input.entity)
|
@@ -341,7 +367,9 @@ def update_gateway_subroute_file(ctx: AnyContext, module_gateway_subroute_path:
|
|
341
367
|
),
|
342
368
|
replace_map={
|
343
369
|
"my_module": snake_module_name,
|
370
|
+
"my-module": kebab_module_name,
|
344
371
|
"my_entity": snake_entity_name,
|
372
|
+
"my-entity": kebab_entity_name,
|
345
373
|
"my_entities": snake_plural_entity_name,
|
346
374
|
"my-entities": kebab_plural_entity_name,
|
347
375
|
"MyEntity": pascal_entity_name,
|
@@ -382,3 +410,35 @@ def _get_import_schema_for_gateway_subroute_code(
|
|
382
410
|
if new_code in existing_code:
|
383
411
|
return None
|
384
412
|
return new_code
|
413
|
+
|
414
|
+
|
415
|
+
def update_gateway_navigation_config_file(
|
416
|
+
ctx: AnyContext, gateway_navigation_config_file_path: str
|
417
|
+
):
|
418
|
+
existing_gateway_navigation_config_code = read_file(
|
419
|
+
gateway_navigation_config_file_path
|
420
|
+
)
|
421
|
+
snake_module_name = to_snake_case(ctx.input.module)
|
422
|
+
kebab_module_name = to_kebab_case(ctx.input.module)
|
423
|
+
kebab_entity_name = to_kebab_case(ctx.input.entity)
|
424
|
+
human_entity_name = to_human_case(ctx.input.entity)
|
425
|
+
kebab_plural_name = to_kebab_case(ctx.input.plural)
|
426
|
+
new_navigation_config_code = read_file(
|
427
|
+
file_path=os.path.join(
|
428
|
+
os.path.dirname(__file__), "template", "navigation_config_file.py"
|
429
|
+
),
|
430
|
+
replace_map={
|
431
|
+
"my_module": snake_module_name,
|
432
|
+
"my-module": kebab_module_name,
|
433
|
+
"my-entity": kebab_entity_name,
|
434
|
+
"My Entity": human_entity_name.title(),
|
435
|
+
"my-entities": kebab_plural_name,
|
436
|
+
},
|
437
|
+
).strip()
|
438
|
+
write_file(
|
439
|
+
file_path=gateway_navigation_config_file_path,
|
440
|
+
content=[
|
441
|
+
existing_gateway_navigation_config_code,
|
442
|
+
new_navigation_config_code,
|
443
|
+
],
|
444
|
+
)
|
@@ -0,0 +1,297 @@
|
|
1
|
+
<link rel="stylesheet" href="/static/crud/style.css">
|
2
|
+
|
3
|
+
<main class="container">
|
4
|
+
<article>
|
5
|
+
<h1>My Entity</h1>
|
6
|
+
|
7
|
+
<fieldset id="crud-table-fieldset" role="group" class="grid">
|
8
|
+
<input id="crud-filter" onchange="applySearch()" placeholder="🔍 Filter" aria-label="Search" />
|
9
|
+
<button onclick="applySearch()">🔍 Search</button>
|
10
|
+
{% if allow_create %}
|
11
|
+
<button class="contrast" onclick="showCreateForm(event)">➕ Add</button>
|
12
|
+
{% endif %}
|
13
|
+
</fieldset>
|
14
|
+
<div id="crud-table-container">
|
15
|
+
<table id="crud-table" class="striped">
|
16
|
+
<thead>
|
17
|
+
<tr>
|
18
|
+
<th scope="col">ID</th>
|
19
|
+
<th scope="col">My Column</th>
|
20
|
+
<!-- Update this -->
|
21
|
+
{% if allow_update or allow_delete %}
|
22
|
+
<th scope="col">Actions</th>
|
23
|
+
{% endif %}
|
24
|
+
</tr>
|
25
|
+
</thead>
|
26
|
+
<tbody></tbody>
|
27
|
+
</table>
|
28
|
+
</div>
|
29
|
+
<div id="crud-pagination"></div>
|
30
|
+
|
31
|
+
{% if allow_create %}
|
32
|
+
<dialog id="crud-create-form-dialog">
|
33
|
+
<article>
|
34
|
+
<h2>New My Entity</h2>
|
35
|
+
<form id="crud-create-form">
|
36
|
+
<label>
|
37
|
+
My Column:
|
38
|
+
<input type="text" name="my_column" required>
|
39
|
+
</label>
|
40
|
+
<!-- Update this -->
|
41
|
+
<footer>
|
42
|
+
<button onclick="createRow(event)">➕ Save</button>
|
43
|
+
<button class="secondary" onclick="hideCreateForm(event)">❌ Cancel</button>
|
44
|
+
</footer>
|
45
|
+
</form>
|
46
|
+
</article>
|
47
|
+
</dialog>
|
48
|
+
{% endif %}
|
49
|
+
|
50
|
+
{% if allow_update %}
|
51
|
+
<dialog id="crud-update-form-dialog">
|
52
|
+
<article>
|
53
|
+
<h2>Update My Entity</h2>
|
54
|
+
<form id="crud-update-form">
|
55
|
+
<label>
|
56
|
+
My Column:
|
57
|
+
<input type="text" name="my_column" required>
|
58
|
+
</label>
|
59
|
+
<!-- Update this -->
|
60
|
+
<footer>
|
61
|
+
<button onclick="updateRow(event)">✏️ Save</button>
|
62
|
+
<button class="secondary" onclick="hideUpdateForm(event)">❌ Cancel</button>
|
63
|
+
</footer>
|
64
|
+
</form>
|
65
|
+
</article>
|
66
|
+
</dialog>
|
67
|
+
{% endif %}
|
68
|
+
|
69
|
+
{% if allow_delete %}
|
70
|
+
<dialog id="crud-delete-form-dialog">
|
71
|
+
<article>
|
72
|
+
<h2>Delete My Entity</h2>
|
73
|
+
<form id="crud-delete-form">
|
74
|
+
<label>
|
75
|
+
My Column:
|
76
|
+
<input type="text" name="my_column" readonly>
|
77
|
+
</label>
|
78
|
+
<!-- Update this -->
|
79
|
+
<footer>
|
80
|
+
<button class="secondary" onclick="hideDeleteForm()">❌ Cancel</button>
|
81
|
+
<button onclick="deleteRow()">🗑️ Delete</button>
|
82
|
+
</footer>
|
83
|
+
</form>
|
84
|
+
</article>
|
85
|
+
</dialog>
|
86
|
+
{% endif %}
|
87
|
+
|
88
|
+
<dialog id="crud-alert-dialog">
|
89
|
+
<article>
|
90
|
+
<h2 id="crud-alert-title">Error</h2>
|
91
|
+
<pre id="crud-alert-message"></pre>
|
92
|
+
<footer>
|
93
|
+
<button onclick="hideAlert(event)">Close</button>
|
94
|
+
</footer>
|
95
|
+
</article>
|
96
|
+
</dialog>
|
97
|
+
|
98
|
+
</article>
|
99
|
+
</main>
|
100
|
+
|
101
|
+
<script src="/static/crud/util.js"></script>
|
102
|
+
<script>
|
103
|
+
const apiUrl = "/api/v1/my-entities";
|
104
|
+
const crudState = {
|
105
|
+
pageSize: {{page_size | tojson}},
|
106
|
+
currentPage: {{page | tojson}},
|
107
|
+
sort: {{sort | tojson}},
|
108
|
+
filter: {{filter | tojson}},
|
109
|
+
allowCreate: {{allow_create | tojson}},
|
110
|
+
allowUpdate: {{allow_update | tojson}},
|
111
|
+
allowDelete: {{allow_delete | tojson}},
|
112
|
+
updatedRowId: null,
|
113
|
+
deletedRowId: null,
|
114
|
+
};
|
115
|
+
|
116
|
+
async function applySearch() {
|
117
|
+
const filterInput = document.getElementById("crud-filter");
|
118
|
+
crudState.filter = filterInput.value;
|
119
|
+
return await fetchRows(crudState.currentPage);
|
120
|
+
}
|
121
|
+
|
122
|
+
async function fetchRows(page = null) {
|
123
|
+
try {
|
124
|
+
if (typeof page !== 'undefined' && page !== null) {
|
125
|
+
crudState.currentPage = page;
|
126
|
+
}
|
127
|
+
const defaultSearchColumn = "my_column"
|
128
|
+
// update address bar
|
129
|
+
const searchParam = CRUD_UTIL.getSearchParam(crudState, defaultSearchColumn, false);
|
130
|
+
const newUrl = `${window.location.pathname}?${searchParam}`;
|
131
|
+
window.history.pushState({ path: newUrl }, "", newUrl);
|
132
|
+
// update table and pagination
|
133
|
+
const apiSearchParam = CRUD_UTIL.getSearchParam(crudState, defaultSearchColumn, true);
|
134
|
+
const result = await UTIL.fetchAPI(
|
135
|
+
`${apiUrl}?${apiSearchParam}`, { method: "GET" }
|
136
|
+
);
|
137
|
+
renderRows(result.data);
|
138
|
+
const crudPagination = document.getElementById("crud-pagination");
|
139
|
+
CRUD_UTIL.renderPagination(
|
140
|
+
crudPagination, crudState, result.count, "fetchRows"
|
141
|
+
);
|
142
|
+
} catch (error) {
|
143
|
+
console.error("Error fetching items:", error);
|
144
|
+
}
|
145
|
+
}
|
146
|
+
|
147
|
+
function renderRows(rows) {
|
148
|
+
const tableBody = document.querySelector("#crud-table tbody");
|
149
|
+
tableBody.innerHTML = "";
|
150
|
+
rows.forEach(row => {
|
151
|
+
let rowComponent = getRowComponents(row);
|
152
|
+
actionColumn = "";
|
153
|
+
if (crudState.allowUpdate) {
|
154
|
+
actionColumn += `<button class="contrast" onclick="showUpdateForm('${row.id}')">✏️ Edit</button>`;
|
155
|
+
}
|
156
|
+
if (crudState.allowDelete) {
|
157
|
+
actionColumn += `<button class="secondary" onclick="showDeleteForm('${row.id}')">🗑️ Delete</button>`;
|
158
|
+
}
|
159
|
+
if (crudState.allowUpdate || crudState.allowDelete) {
|
160
|
+
actionColumn = `<td><fieldset class="grid" role="group">${actionColumn}</fieldset></td>`;
|
161
|
+
}
|
162
|
+
tableBody.innerHTML += `<tr>${rowComponent.join('')}${actionColumn}</tr>`;
|
163
|
+
});
|
164
|
+
}
|
165
|
+
|
166
|
+
function getRowComponents(row) {
|
167
|
+
let rowComponents = [];
|
168
|
+
rowComponents.push(`<td>${row.id}</td>`);
|
169
|
+
rowComponents.push(`<td>${row.my_column}</td>`);
|
170
|
+
// Update this
|
171
|
+
return rowComponents;
|
172
|
+
}
|
173
|
+
|
174
|
+
{% if allow_create %}
|
175
|
+
async function showCreateForm(id) {
|
176
|
+
const createFormDialog = document.getElementById("crud-create-form-dialog");
|
177
|
+
const createForm = document.getElementById("crud-create-form");
|
178
|
+
UTIL.clearFormData(createForm);
|
179
|
+
createFormDialog.showModal();
|
180
|
+
}
|
181
|
+
|
182
|
+
async function createRow(event = null) {
|
183
|
+
if (event != null) {
|
184
|
+
event.preventDefault();
|
185
|
+
}
|
186
|
+
try {
|
187
|
+
const createForm = document.getElementById("crud-create-form");
|
188
|
+
const formData = JSON.stringify(UTIL.getFormData(createForm));
|
189
|
+
await UTIL.fetchAPI(apiUrl, {method: "POST", body: formData});
|
190
|
+
await fetchRows();
|
191
|
+
hideCreateForm();
|
192
|
+
} catch(error) {
|
193
|
+
showAlert("Create My Entity Error", error);
|
194
|
+
}
|
195
|
+
}
|
196
|
+
|
197
|
+
function hideCreateForm(event = null) {
|
198
|
+
if (event != null) {
|
199
|
+
event.preventDefault();
|
200
|
+
}
|
201
|
+
const createFormDialog = document.getElementById("crud-create-form-dialog");
|
202
|
+
createFormDialog.close();
|
203
|
+
}
|
204
|
+
{% endif %}
|
205
|
+
|
206
|
+
{% if allow_update %}
|
207
|
+
async function showUpdateForm(id) {
|
208
|
+
crudState.updatedRowId = id;
|
209
|
+
const updateFormDialog = document.getElementById("crud-update-form-dialog");
|
210
|
+
const updateForm = document.getElementById("crud-update-form");
|
211
|
+
result = await UTIL.fetchAPI(`${apiUrl}/${id}`, { method: "GET" });
|
212
|
+
UTIL.setFormData(updateForm, result);
|
213
|
+
updateFormDialog.showModal();
|
214
|
+
}
|
215
|
+
|
216
|
+
async function updateRow(event = null) {
|
217
|
+
if (event != null) {
|
218
|
+
event.preventDefault();
|
219
|
+
}
|
220
|
+
try {
|
221
|
+
const updateForm = document.getElementById("crud-update-form");
|
222
|
+
const formData = JSON.stringify(UTIL.getFormData(updateForm));
|
223
|
+
await UTIL.fetchAPI(
|
224
|
+
`${apiUrl}/${crudState.updatedRowId}`, {method: "PUT", body: formData},
|
225
|
+
);
|
226
|
+
await fetchRows();
|
227
|
+
hideUpdateForm();
|
228
|
+
} catch(error) {
|
229
|
+
showAlert("Update My Entity Error", error);
|
230
|
+
}
|
231
|
+
}
|
232
|
+
|
233
|
+
function hideUpdateForm(event = null) {
|
234
|
+
if (event != null) {
|
235
|
+
event.preventDefault();
|
236
|
+
}
|
237
|
+
const updateFormDialog = document.getElementById("crud-update-form-dialog");
|
238
|
+
updateFormDialog.close();
|
239
|
+
}
|
240
|
+
{% endif %}
|
241
|
+
|
242
|
+
{% if allow_delete %}
|
243
|
+
async function showDeleteForm(id) {
|
244
|
+
crudState.deletedRowId = id;
|
245
|
+
const deleteFormDialog = document.getElementById("crud-delete-form-dialog");
|
246
|
+
const deleteForm = document.getElementById("crud-delete-form");
|
247
|
+
result = await UTIL.fetchAPI(`${apiUrl}/${id}`, { method: "GET" });
|
248
|
+
UTIL.setFormData(deleteForm, result);
|
249
|
+
deleteFormDialog.showModal();
|
250
|
+
}
|
251
|
+
|
252
|
+
async function deleteRow(event = null) {
|
253
|
+
if (event != null) {
|
254
|
+
event.preventDefault();
|
255
|
+
}
|
256
|
+
try {
|
257
|
+
await UTIL.fetchAPI(`${apiUrl}/${crudState.deletedRowId}`, {method: "DELETE",});
|
258
|
+
await fetchRows();
|
259
|
+
hideDeleteForm();
|
260
|
+
} catch(error) {
|
261
|
+
showAlert("Delete My Entity Error", error);
|
262
|
+
}
|
263
|
+
}
|
264
|
+
|
265
|
+
function hideDeleteForm(event = null) {
|
266
|
+
if (event != null) {
|
267
|
+
event.preventDefault();
|
268
|
+
}
|
269
|
+
const deleteFormDialog = document.getElementById("crud-delete-form-dialog");
|
270
|
+
deleteFormDialog.close();
|
271
|
+
}
|
272
|
+
{% endif %}
|
273
|
+
|
274
|
+
function showAlert(title, error) {
|
275
|
+
const alertDialog = document.getElementById("crud-alert-dialog");
|
276
|
+
const alertTitle = document.getElementById("crud-alert-title");
|
277
|
+
const alertMessage = document.getElementById("crud-alert-message");
|
278
|
+
const errorMessage = error.message ? error.message : String(error);
|
279
|
+
alertTitle.textContent = title;
|
280
|
+
alertMessage.textContent = errorMessage;
|
281
|
+
alertDialog.showModal();
|
282
|
+
}
|
283
|
+
|
284
|
+
function hideAlert(event = null) {
|
285
|
+
if (event != null) {
|
286
|
+
event.preventDefault();
|
287
|
+
}
|
288
|
+
const alertDialog = document.getElementById("crud-alert-dialog");
|
289
|
+
alertDialog.close();
|
290
|
+
}
|
291
|
+
|
292
|
+
document.addEventListener("DOMContentLoaded", () => {
|
293
|
+
const filterInput = document.getElementById("crud-filter");
|
294
|
+
filterInput.value = crudState.filter;
|
295
|
+
fetchRows(crudState.currentPage);
|
296
|
+
});
|
297
|
+
</script>
|
@@ -1,6 +1,30 @@
|
|
1
1
|
# MyEntity routes
|
2
2
|
|
3
3
|
|
4
|
+
@app.get("/my-module/my-entities", include_in_schema=False)
|
5
|
+
def my_entities_crud_ui(
|
6
|
+
current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
|
7
|
+
page: int = 1,
|
8
|
+
page_size: int = 10,
|
9
|
+
sort: str | None = None,
|
10
|
+
filter: str | None = None,
|
11
|
+
):
|
12
|
+
if not current_user.has_permission("my-entity:read"):
|
13
|
+
return render_error(error_message="Access denied", status_code=403)
|
14
|
+
return render_content(
|
15
|
+
view_path=os.path.join("my-module", "my-entity.html"),
|
16
|
+
current_user=current_user,
|
17
|
+
page_name="my-module.my-entity",
|
18
|
+
page=page,
|
19
|
+
page_size=page_size,
|
20
|
+
sort=sort,
|
21
|
+
filter=filter,
|
22
|
+
allow_create=current_user.has_permission("my-entity:create"),
|
23
|
+
allow_update=current_user.has_permission("my-entity:update"),
|
24
|
+
allow_delete=current_user.has_permission("my-entity:delete"),
|
25
|
+
)
|
26
|
+
|
27
|
+
|
4
28
|
@app.get("/api/v1/my-entities", response_model=MultipleMyEntityResponse)
|
5
29
|
async def get_my_entities(
|
6
30
|
current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
|
@@ -8,7 +8,7 @@ run_env_input = OptionInput(
|
|
8
8
|
description="Running environment",
|
9
9
|
prompt="Running Environment",
|
10
10
|
options=["dev", "prod"],
|
11
|
-
|
11
|
+
default="prod",
|
12
12
|
)
|
13
13
|
|
14
14
|
new_module_input = StrInput(
|
@@ -37,14 +37,14 @@ plural_entity_input = StrInput(
|
|
37
37
|
name="plural",
|
38
38
|
description="Plural entity name",
|
39
39
|
prompt="Plural entity name",
|
40
|
-
|
40
|
+
default=lambda ctx: pluralize(ctx.input.entity),
|
41
41
|
)
|
42
42
|
|
43
43
|
new_entity_column_input = StrInput(
|
44
44
|
name="column",
|
45
45
|
description="Entity's column name",
|
46
46
|
prompt="New entity's column name",
|
47
|
-
|
47
|
+
default="name",
|
48
48
|
)
|
49
49
|
|
50
50
|
new_column_input = StrInput(
|
@@ -10,12 +10,14 @@ from my_app_name._zrb.module.add_module_util import (
|
|
10
10
|
is_app_zrb_config_file,
|
11
11
|
is_app_zrb_task_file,
|
12
12
|
is_gateway_module_subroute_file,
|
13
|
+
is_gateway_navigation_config_file,
|
13
14
|
is_gateway_route_file,
|
14
15
|
is_in_module_dir,
|
15
16
|
update_app_config_file,
|
16
17
|
update_app_main_file,
|
17
18
|
update_app_zrb_config_file,
|
18
19
|
update_app_zrb_task_file,
|
20
|
+
update_gateway_navigation_config_file,
|
19
21
|
update_gateway_route_file,
|
20
22
|
)
|
21
23
|
from my_app_name._zrb.util import get_existing_module_names
|
@@ -91,6 +93,12 @@ scaffold_my_app_name_module = Scaffolder(
|
|
91
93
|
match=is_gateway_route_file,
|
92
94
|
transform=update_gateway_route_file,
|
93
95
|
),
|
96
|
+
# Register module's page to my_app_name/gateway/config/navigation.py
|
97
|
+
ContentTransformer(
|
98
|
+
name="transform-gateway-navigation-config",
|
99
|
+
match=is_gateway_navigation_config_file,
|
100
|
+
transform=update_gateway_navigation_config_file,
|
101
|
+
),
|
94
102
|
],
|
95
103
|
retries=0,
|
96
104
|
upstream=validate_add_my_app_name_module,
|
@@ -7,7 +7,12 @@ from zrb.context.any_context import AnyContext
|
|
7
7
|
from zrb.util.codemod.modify_dict import append_key_to_dict
|
8
8
|
from zrb.util.codemod.modify_function import append_code_to_function
|
9
9
|
from zrb.util.file import read_file, write_file
|
10
|
-
from zrb.util.string.conversion import
|
10
|
+
from zrb.util.string.conversion import (
|
11
|
+
to_human_case,
|
12
|
+
to_kebab_case,
|
13
|
+
to_pascal_case,
|
14
|
+
to_snake_case,
|
15
|
+
)
|
11
16
|
|
12
17
|
|
13
18
|
def is_app_config_file(ctx: AnyContext, file_path: str) -> bool:
|
@@ -22,6 +27,12 @@ def is_gateway_route_file(ctx: AnyContext, file_path: str) -> bool:
|
|
22
27
|
return file_path == os.path.join(APP_DIR, "module", "gateway", "route.py")
|
23
28
|
|
24
29
|
|
30
|
+
def is_gateway_navigation_config_file(ctx: AnyContext, file_path: str) -> bool:
|
31
|
+
return file_path == os.path.join(
|
32
|
+
APP_DIR, "module", "gateway", "config", "navigation.py"
|
33
|
+
)
|
34
|
+
|
35
|
+
|
25
36
|
def is_app_zrb_task_file(ctx: AnyContext, file_path: str) -> bool:
|
26
37
|
return file_path == os.path.join(APP_DIR, "_zrb", "task.py")
|
27
38
|
|
@@ -194,3 +205,31 @@ def _get_module_subroute_import(existing_code: str, module_name: str) -> str | N
|
|
194
205
|
if import_code in existing_code:
|
195
206
|
return None
|
196
207
|
return import_code
|
208
|
+
|
209
|
+
|
210
|
+
def update_gateway_navigation_config_file(
|
211
|
+
ctx: AnyContext, gateway_navigation_config_file_path: str
|
212
|
+
):
|
213
|
+
existing_gateway_navigation_config_code = read_file(
|
214
|
+
gateway_navigation_config_file_path
|
215
|
+
)
|
216
|
+
snake_module_name = to_snake_case(ctx.input.module)
|
217
|
+
kebab_module_name = to_kebab_case(ctx.input.module)
|
218
|
+
human_module_name = to_human_case(ctx.input.module)
|
219
|
+
new_navigation_config_code = read_file(
|
220
|
+
file_path=os.path.join(
|
221
|
+
os.path.dirname(__file__), "template", "navigation_config_file.py"
|
222
|
+
),
|
223
|
+
replace_map={
|
224
|
+
"my_module": snake_module_name,
|
225
|
+
"my-module": kebab_module_name,
|
226
|
+
"My Module": human_module_name.title(),
|
227
|
+
},
|
228
|
+
).strip()
|
229
|
+
write_file(
|
230
|
+
file_path=gateway_navigation_config_file_path,
|
231
|
+
content=[
|
232
|
+
existing_gateway_navigation_config_code,
|
233
|
+
new_navigation_config_code,
|
234
|
+
],
|
235
|
+
)
|
@@ -1,8 +1,10 @@
|
|
1
|
+
import os
|
1
2
|
from typing import Annotated
|
2
3
|
|
3
4
|
from fastapi import Depends, FastAPI
|
4
5
|
from my_app_name.common.error import ForbiddenError
|
5
6
|
from my_app_name.module.gateway.util.auth import get_current_user
|
7
|
+
from my_app_name.module.gateway.util.view import render_content, render_error
|
6
8
|
from my_app_name.schema.user import AuthUserResponse
|
7
9
|
|
8
10
|
|
@@ -72,7 +72,7 @@ def create_default_filter_param_parser() -> (
|
|
72
72
|
# Returns [UserModel.age >= 18, UserModel.name.like("John%"), UserModel.role.in_(["admin", "user"]), UserModel.address == "123, Main St."]
|
73
73
|
"""
|
74
74
|
filters: list[ClauseElement] = []
|
75
|
-
filter_parts =
|
75
|
+
filter_parts = split_unescaped(query)
|
76
76
|
for part in filter_parts:
|
77
77
|
match = re.match(r"(.+):(.+):(.+)", part)
|
78
78
|
if match:
|
@@ -101,5 +101,5 @@ def create_default_filter_param_parser() -> (
|
|
101
101
|
return parse_filter_param
|
102
102
|
|
103
103
|
|
104
|
-
def
|
104
|
+
def split_unescaped(s: str, delimiter: str = ",") -> list[str]:
|
105
105
|
return re.split(r"(?<!\\)" + re.escape(delimiter), s)
|
@@ -27,7 +27,7 @@ def render_str(template_path: str, **data: Any) -> str:
|
|
27
27
|
def render_page(
|
28
28
|
template_path: str,
|
29
29
|
status_code: int = 200,
|
30
|
-
headers: dict[str, str] = None,
|
30
|
+
headers: dict[str, str] | None = None,
|
31
31
|
media_type: str | None = None,
|
32
32
|
**data: Any
|
33
33
|
) -> HTMLResponse:
|
@@ -8,25 +8,35 @@ APP_VERSION = "0.1.0"
|
|
8
8
|
|
9
9
|
APP_GATEWAY_VIEW_PATH = os.path.join(APP_PATH, "module", "gateway", "view")
|
10
10
|
APP_GATEWAY_VIEW_DEFAULT_TEMPLATE_PATH = os.getenv(
|
11
|
-
"
|
11
|
+
"MY_APP_NAME_GATEWAY_VIEW_DEFAULT_TEMPLATE_PATH",
|
12
12
|
os.path.join("template", "default.html"),
|
13
13
|
)
|
14
|
-
|
14
|
+
APP_GATEWAY_PICO_CSS_COLOR = os.getenv("MY_APP_NAME_GATEWAY_PICO_CSS_COLOR", "")
|
15
|
+
APP_GATEWAY_PICO_CSS_PATH = (
|
16
|
+
"/static/pico-css/pico.min.css"
|
17
|
+
if APP_GATEWAY_PICO_CSS_COLOR == ""
|
18
|
+
else f"/static/pico-css/pico.{APP_GATEWAY_PICO_CSS_COLOR}.min.css"
|
19
|
+
)
|
15
20
|
APP_GATEWAY_CSS_PATH_LIST = [
|
16
21
|
path
|
17
|
-
for path in os.getenv("
|
22
|
+
for path in os.getenv("MY_APP_NAME_GATEWAY_CSS_PATH", "").split(":")
|
18
23
|
if path != ""
|
19
24
|
]
|
20
25
|
APP_GATEWAY_JS_PATH_LIST = [
|
21
|
-
path
|
26
|
+
path
|
27
|
+
for path in os.getenv("MY_APP_NAME_GATEWAY_JS_PATH", "").split(":")
|
28
|
+
if path != ""
|
22
29
|
]
|
23
|
-
APP_GATEWAY_TITLE = os.getenv("
|
24
|
-
APP_GATEWAY_SUBTITLE = os.getenv("
|
30
|
+
APP_GATEWAY_TITLE = os.getenv("MY_APP_NAME_GATEWAY_TITLE", "My App Name")
|
31
|
+
APP_GATEWAY_SUBTITLE = os.getenv("MY_APP_NAME_GATEWAY_SUBTITLE", "Just Another App")
|
25
32
|
APP_GATEWAY_LOGO_PATH = os.getenv(
|
26
|
-
"
|
33
|
+
"MY_APP_NAME_GATEWAY_LOGO", "/static/images/android-chrome-192x192.png"
|
34
|
+
)
|
35
|
+
APP_GATEWAY_FOOTER = os.getenv(
|
36
|
+
"MY_APP_NAME_GATEWAY_FOOTER", f"{APP_GATEWAY_TITLE} © 2025"
|
27
37
|
)
|
28
38
|
APP_GATEWAY_FAVICON_PATH = os.getenv(
|
29
|
-
"
|
39
|
+
"MY_APP_NAME_GATEWAY_FAVICON", "/static/images/favicon-32x32.png"
|
30
40
|
)
|
31
41
|
|
32
42
|
APP_MODE = os.getenv("MY_APP_NAME_MODE", "monolith")
|
zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/config/navigation.py
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
from my_app_name.module.gateway.schema.navigation import Navigation, Page, PageGroup
|
2
|
+
|
3
|
+
APP_NAVIGATION = Navigation()
|
4
|
+
|
5
|
+
APP_NAVIGATION.append_page(Page(name="gateway.home", caption="Home", url="/"))
|
6
|
+
|
7
|
+
auth_menu = APP_NAVIGATION.append_page_group(
|
8
|
+
PageGroup(
|
9
|
+
name="auth",
|
10
|
+
caption="Authorization",
|
11
|
+
)
|
12
|
+
)
|
13
|
+
|
14
|
+
auth_menu.append_page(
|
15
|
+
Page(
|
16
|
+
name="auth.permission",
|
17
|
+
caption="Permission",
|
18
|
+
url="/auth/permissions",
|
19
|
+
permission="permission:read",
|
20
|
+
)
|
21
|
+
)
|
22
|
+
|
23
|
+
auth_menu.append_page(
|
24
|
+
Page(
|
25
|
+
name="auth.role",
|
26
|
+
caption="Role",
|
27
|
+
url="/auth/roles",
|
28
|
+
permission="role:read",
|
29
|
+
)
|
30
|
+
)
|
31
|
+
|
32
|
+
auth_menu.append_page(
|
33
|
+
Page(
|
34
|
+
name="auth.user",
|
35
|
+
caption="User",
|
36
|
+
url="/auth/users",
|
37
|
+
permission="user:read",
|
38
|
+
)
|
39
|
+
)
|