cmdbox 0.5.2__py3-none-any.whl → 0.5.3__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.

Potentially problematic release.


This version of cmdbox might be problematic. Click here for more details.

Files changed (51) hide show
  1. cmdbox/app/auth/signin.py +1 -0
  2. cmdbox/app/features/cli/audit_base.py +4 -1
  3. cmdbox/app/features/cli/cmdbox_audit_delete.py +27 -18
  4. cmdbox/app/features/cli/cmdbox_audit_search.py +128 -62
  5. cmdbox/app/features/cli/cmdbox_audit_write.py +27 -20
  6. cmdbox/app/features/web/cmdbox_web_audit.py +81 -0
  7. cmdbox/app/features/web/cmdbox_web_audit_metrics.py +72 -0
  8. cmdbox/app/features/web/cmdbox_web_exec_cmd.py +10 -5
  9. cmdbox/app/features/web/cmdbox_web_user_data.py +58 -0
  10. cmdbox/app/options.py +40 -19
  11. cmdbox/app/web.py +7 -1
  12. cmdbox/extensions/features.yml +7 -4
  13. cmdbox/extensions/user_list.yml +5 -0
  14. cmdbox/licenses/LICENSE.argcomplete.3.6.2(Apache Software License).txt +177 -0
  15. cmdbox/licenses/LICENSE.gevent.25.4.1(MIT).txt +25 -0
  16. cmdbox/licenses/LICENSE.greenlet.3.2.0(MIT AND Python-2.0).txt +30 -0
  17. cmdbox/licenses/LICENSE.pillow.11.2.1(UNKNOWN).txt +1200 -0
  18. cmdbox/licenses/LICENSE.prompt_toolkit.3.0.51(BSD License).txt +27 -0
  19. cmdbox/licenses/LICENSE.pydantic.2.11.3(MIT License).txt +21 -0
  20. cmdbox/licenses/LICENSE.pydantic_core.2.33.1(MIT License).txt +21 -0
  21. cmdbox/licenses/LICENSE.starlette.0.46.2(BSD License).txt +27 -0
  22. cmdbox/licenses/LICENSE.typing_extensions.4.13.2(UNKNOWN).txt +279 -0
  23. cmdbox/licenses/LICENSE.urllib3.2.4.0(UNKNOWN).txt +21 -0
  24. cmdbox/licenses/LICENSE.uvicorn.0.34.1(BSD License).txt +27 -0
  25. cmdbox/licenses/LICENSE.watchfiles.1.0.5(MIT License).txt +21 -0
  26. cmdbox/licenses/files.txt +12 -13
  27. cmdbox/version.py +2 -2
  28. cmdbox/web/assets/apexcharts/apexcharts.css +679 -0
  29. cmdbox/web/assets/apexcharts/apexcharts.min.js +38 -0
  30. cmdbox/web/assets/cmdbox/audit.js +340 -0
  31. cmdbox/web/assets/cmdbox/color_mode.css +4 -0
  32. cmdbox/web/assets/cmdbox/common.js +397 -24
  33. cmdbox/web/assets/cmdbox/filer_modal.js +1 -1
  34. cmdbox/web/assets/cmdbox/list_cmd.js +7 -271
  35. cmdbox/web/assets/cmdbox/list_pipe.js +3 -3
  36. cmdbox/web/assets/cmdbox/users.js +17 -17
  37. cmdbox/web/assets/cmdbox/view_raw.js +1 -1
  38. cmdbox/web/assets/cmdbox/view_result.js +11 -13
  39. cmdbox/web/assets/filer/filer.js +2 -2
  40. cmdbox/web/assets_license_list.txt +4 -1
  41. cmdbox/web/audit.html +268 -0
  42. cmdbox/web/filer.html +21 -10
  43. cmdbox/web/gui.html +21 -52
  44. cmdbox/web/result.html +9 -2
  45. cmdbox/web/users.html +7 -3
  46. {cmdbox-0.5.2.dist-info → cmdbox-0.5.3.dist-info}/METADATA +8 -5
  47. {cmdbox-0.5.2.dist-info → cmdbox-0.5.3.dist-info}/RECORD +51 -32
  48. {cmdbox-0.5.2.dist-info → cmdbox-0.5.3.dist-info}/LICENSE +0 -0
  49. {cmdbox-0.5.2.dist-info → cmdbox-0.5.3.dist-info}/WHEEL +0 -0
  50. {cmdbox-0.5.2.dist-info → cmdbox-0.5.3.dist-info}/entry_points.txt +0 -0
  51. {cmdbox-0.5.2.dist-info → cmdbox-0.5.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,340 @@
1
+ const audit = {};
2
+ // 監査ログ一覧
3
+ audit.rawlog = async () => {
4
+ const form = $('#filter_form');
5
+ const rawlog_area = $('#rawlog_area').html('');
6
+ const [title, opt] = cmdbox.get_param(form);
7
+ cmdbox.show_loading();
8
+ const data = await audit.query(opt);
9
+ if (!data) {
10
+ cmdbox.hide_loading();
11
+ return;
12
+ }
13
+ render_result_func(rawlog_area, data, 100);
14
+ audit.tracelog(data);
15
+ await audit.metrics();
16
+ cmdbox.hide_loading();
17
+ };
18
+ // 監査ログのトレース
19
+ audit.tracelog = async (data) => {
20
+ const rawlog_area = $('#trace_area').html('');
21
+ const table = $('<table class="table table-bordered table-hover table-sm"></table>').appendTo(rawlog_area);
22
+ const table_head = $('<thead><tr></tr></thead>').appendTo(table).find('tr');
23
+ const table_body = $('<tbody></tbody>').appendTo(table);
24
+ table_head.append($('<th class="th" scope="col">clmsg_user</th>'));
25
+ table_head.append($('<th class="th" scope="col">trace log</th>'));
26
+ const row_dict = {};
27
+ for (const row of data) {
28
+ const clmsg_id = row['clmsg_id'];
29
+ if (clmsg_id == null || clmsg_id == '') continue;
30
+ if (!row_dict[clmsg_id]) {
31
+ row_dict[clmsg_id] = {clmsg_id:clmsg_id, clmsg_user:row['clmsg_user'], clmsg_date:row['clmsg_date'], row:[]};
32
+ }
33
+ if (!row_dict[clmsg_id]['clmsg_date']) {
34
+ row_dict[clmsg_id]['clmsg_date'] = row['clmsg_date'];
35
+ }
36
+ if (!row_dict[clmsg_id]['clmsg_user']) {
37
+ row_dict[clmsg_id]['clmsg_user'] = row['clmsg_user'];
38
+ }
39
+ delete row['clmsg_id'];
40
+ delete row['clmsg_user'];
41
+ row_dict[clmsg_id]['row'].push(row);
42
+ }
43
+ Object.values(row_dict).sort((a,b) => {
44
+ a['clmsg_date'] > b['clmsg_date'] ? 1 : -1;
45
+ }).forEach((attr, i) => {
46
+ const tr = $('<tr></tr>').appendTo(table_body);
47
+ $(`<td>${attr['clmsg_user']}</td>`).appendTo(tr);
48
+ const div = $(`<td><span>clmsg_id : ${attr['clmsg_id']}</span><div/></td>`).appendTo(tr).find('div');
49
+ render_result_func(div, attr['row'], 100);
50
+ });
51
+ };
52
+ // 検索
53
+ audit.query = async (opt) => {
54
+ const res = await fetch(`audit/rawlog`,
55
+ {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(opt)});
56
+ if (res.status != 200) {
57
+ cmdbox.message({'error':`${res.status}: ${res.statusText}`});
58
+ return;
59
+ }
60
+ try {
61
+ const content = JSON.parse(await res.text());
62
+ if (!content['success']) {
63
+ cmdbox.message({'error': content});
64
+ return;
65
+ }
66
+ return content['success']['data'];
67
+ } catch (e) {
68
+ cmdbox.message({'error': e.message});
69
+ return;
70
+ }
71
+ };
72
+ // メトリクスの表示
73
+ audit.metrics = async () => {
74
+ const metrics_area = $('#metrics_area');
75
+ metrics_area.html('');
76
+ audit.list_audit_metrics().then((res) => {
77
+ if (!res['success']) return;
78
+ res['success'].forEach(async (row) => {
79
+ const form = $('#filter_form');
80
+ const [_, opt] = cmdbox.get_param(form);
81
+ opt['select'] = row['vertical'];
82
+ opt['select'][row['horizontal']] = '-';
83
+ opt['select_date_format'] = row['horizontal_date_format'];
84
+ opt['groupby'] = [row['horizontal']];
85
+ opt['groupby_date_format'] = row['horizontal_date_format'];
86
+ opt['sort'][row['horizontal']] = 'DESC';
87
+ let data = await audit.query(opt);
88
+ if (!data) return;
89
+ data = data.reverse();
90
+ // 時系列グラフの追加
91
+ const card = $(`<div class="col-${row['col_size']} p-1"><div class="card card-hover"><div class="card-body"></div></div></div>`).appendTo(metrics_area);
92
+ const card_body = card.find('.card-body');
93
+ const title = $(`<div class="d-flex">`
94
+ + `<button id="edit_metrics" type="button" class="btn p-0 me-3">`
95
+ + `<svg width="32" height="32" fill="currentColor" class="bi bi-plus-lg"><use href="#svg_edit_btn"></use></svg></button>`
96
+ + `<h5 class="d-inline-block m-0">${row['title']}</h5></div>`).appendTo(card_body);
97
+ const edit_btn = card_body.find('#edit_metrics');
98
+ const graph = $(`<div class="chart"></div>`).appendTo(card_body);
99
+ const series = [];
100
+ for (const vk of Object.keys(row['vertical'])) {
101
+ if (vk == row['horizontal']) continue;
102
+ const sel = {name:vk, data:[...data.map(d => d[vk])]};
103
+ series.push(sel);
104
+ }
105
+ const categories = [...data.map(d => d[row['horizontal']])];
106
+ const chart_opt = {
107
+ chart: {
108
+ type: row['chart_type'],
109
+ stacked: row['chart_stacked'],
110
+ },
111
+ stroke: {
112
+ show: true,
113
+ curve: row['stroke_curve'],
114
+ width: row['stroke_width'],
115
+ },
116
+ series: series,
117
+ tooltip: {
118
+ theme: false
119
+ },
120
+ xaxis: {
121
+ categories: categories
122
+ }
123
+ }
124
+ const chart = new ApexCharts(graph.get(0), chart_opt);
125
+ chart.render();
126
+ graph.find('.apexcharts-toolbar').on('click', (e) => {
127
+ e.stopPropagation();
128
+ e.preventDefault();
129
+ });
130
+ edit_btn.off('click').on('click', (e) => {
131
+ const title = row['title'];
132
+ audit.metrics_modal_func(title);
133
+ e.stopPropagation();
134
+ });
135
+ });
136
+ });
137
+ };
138
+ // メトリクスのモーダルダイアログを表示
139
+ audit.metrics_modal_func = (title) => {
140
+ const modal = $('#metrics_modal');
141
+ modal.find('.modal-title').text(title ? `Edit Metrics : ${title}` : 'New Metrics');
142
+ const row_content = modal.find('.row_content');
143
+ row_content.empty();
144
+ audit.load_audit_metrics(title?title:null).then((res) => {
145
+ const axis = ['','audit_type', 'clmsg_id', 'clmsg_date', 'clmsg_src', 'clmsg_title', 'clmsg_user', 'clmsg_body', 'clmsg_tag', 'svmsg_id', 'svmsg_date'];
146
+ const chart_type = ['','line', 'area', 'bar'];
147
+ const stroke_curve = ['','smooth', 'straight', 'stepline'];
148
+ const data = res['success']?res['success']:{};
149
+ const rows = [
150
+ {opt:'title', type:'str', default:title?title:'', required:true, multi:false, hide:false, choice:null},
151
+ {opt:'chart_type', type:'str', default:data['chart_type']?data['chart_type']:'line', required:true, multi:false, hide:false, choice:chart_type,
152
+ choice_show: {
153
+ 'line':['stroke_curve','stroke_width'],
154
+ 'area':['stroke_curve','chart_stacked','stroke_width'],
155
+ 'bar':['chart_stacked']}},
156
+ {opt:'stroke_curve', type:'str', default:data['stroke_curve']?data['stroke_curve']:'straight', required:false, multi:false, hide:false, choice:stroke_curve},
157
+ {opt:'chart_stacked', type:'bool', default:data['chart_stacked']?data['chart_stacked']:false, required:false, multi:false, hide:false, choice:[false, true]},
158
+ {opt:'stroke_width', type:'int', default:data['stroke_width']?data['stroke_width']:2, required:false, multi:false, hide:false, choice:[...Array(5).keys()].map(i => i+1)},
159
+ {opt:'col_size', type:'int', default:data['col_size']?data['col_size']:6, required:true, multi:false, hide:false, choice:[...Array(12).keys()].map(i => i+1)},
160
+ {opt:'horizontal', type:'str', default:data['horizontal']?data['horizontal']:'svmsg_date', required:true, multi:false, hide:false, choice:axis,
161
+ choice_show: {
162
+ 'clmsg_date':['horizontal_date_format'],
163
+ 'svmsg_date':['horizontal_date_format']}},
164
+ {opt:'horizontal_date_format', type:'str', default:data['horizontal_date_format']?data['horizontal_date_format']:'%Y/%m/%d',
165
+ required:false, multi:false, hide:false, choice:['','%Y/%m/%d %H:%M', '%Y/%m/%d %H', '%Y/%m/%d', '%Y/%m', '%Y', '%m', '%w']},
166
+ ];
167
+ data['vertical'] = data['vertical'] || {'clmsg_id':'count'};
168
+ Object.keys(data['vertical']).forEach((key) => {
169
+ const def = {};
170
+ def[key] = data['vertical'][key];
171
+ rows.push({opt:'vertical', type:'dict', default:def, required:true, multi:true, hide:false,
172
+ choice:{'key':axis,'val':['-','count','sum','avg','min','max']}});
173
+ });
174
+ rows.forEach((row, i) => cmdbox.add_form_func(i, modal, row_content, row, null, 12, 6));
175
+ title && modal.find('[name="title"]').prop('readonly', true);
176
+ modal.find('.choice_show').change();
177
+ });
178
+ // 保存実行
179
+ modal.find('#metrics_save').off('click').on('click', async () => {
180
+ const [title, opt] = cmdbox.get_param(modal);
181
+ if (!title || title == '') {
182
+ cmdbox.message({'error': 'Title is required'});
183
+ return;
184
+ }
185
+ if (!opt['chart_type'] || opt['chart_type'] == '') {
186
+ cmdbox.message({'error': 'chart_type is required'});
187
+ return;
188
+ }
189
+ if (!opt['col_size'] || opt['col_size'] == '') {
190
+ cmdbox.message({'error': 'col_size is required'});
191
+ return;
192
+ }
193
+ if (!opt['horizontal'] || opt['horizontal'] == '') {
194
+ cmdbox.message({'error': 'horizontal is required'});
195
+ return;
196
+ }
197
+ if (!opt['vertical'] || opt['vertical'] == '') {
198
+ cmdbox.message({'error': 'vertical is required'});
199
+ return;
200
+ }
201
+ if (!window.confirm('Do you want to save?')) return;
202
+ await audit.save_audit_metrics(title, opt);
203
+ await audit.metrics();
204
+ modal.modal('hide');
205
+ });
206
+ // 削除実行
207
+ modal.find('#metrics_del').off('click').on('click', async () => {
208
+ if (!window.confirm('Do you want to delete?')) return;
209
+ await audit.del_audit_metrics(title);
210
+ await audit.metrics();
211
+ modal.modal('hide');
212
+ });
213
+ if (!title) modal.find('#metrics_del').hide();
214
+ else modal.find('#metrics_del').show();
215
+ modal.modal('show');
216
+ };
217
+ // 監査ログのフィルターフォームの初期化
218
+ audit.init_form = async () => {
219
+ // フォームの初期化
220
+ const form = $('#filter_form');
221
+ const row_content = form.find('.row_content');
222
+ const res = await fetch('audit/mode_cmd', {method: 'GET'});
223
+ if (res.status != 200) cmdbox.message({'error':`${res.status}: ${res.statusText}`});
224
+ const msg = await res.json();
225
+ if (!msg['success']) {
226
+ cmdbox.message({'error': msg['message']});
227
+ return;
228
+ }
229
+ const args = msg['success'];
230
+ const py_get_cmd_choices = await cmdbox.get_cmd_choices(args['mode'], args['cmd']);
231
+ row_content.html('');
232
+ // 検索ボタンを表示
233
+ const search_btn = $('<button type="button" class="btn btn-primary col-11 m-3">Search</button>').appendTo(row_content);
234
+ search_btn.off('click').on('click', (e) => {
235
+ audit.rawlog();
236
+ const condition = {};
237
+ row_content.find(':input').each((i, el) => {
238
+ const elem = $(el);
239
+ const id = elem.attr('id');
240
+ const val = elem.val();
241
+ if (!id || id == '') return;
242
+ if (!val || val == '') return;
243
+ condition[id] = {'name': elem.attr('name'), 'value': val,
244
+ 'param_data_index': elem.attr('param_data_index'),
245
+ 'param_data_type': elem.attr('param_data_type'),
246
+ 'param_data_multi': elem.attr('param_data_multi')};
247
+ });
248
+ cmdbox.save_user_data('audit', 'condition', JSON.stringify(condition));
249
+ });
250
+ // 主なフィルター条件のフォームを表示
251
+ const nml_conditions = ['filter_audit_type', 'filter_clmsg_id', 'filter_clmsg_src', 'filter_clmsg_title', 'filter_clmsg_title', 'filter_clmsg_user',
252
+ 'filter_clmsg_tag', 'filter_svmsg_sdate', 'filter_svmsg_edate']
253
+ py_get_cmd_choices.filter(row => nml_conditions.includes(row.opt)).forEach((row, i) => cmdbox.add_form_func(i, form, row_content, row, null, 12, 12));
254
+ const adv_link = $('<div class="text-center card-hover col-12 mb-3"><a href="#">[ advanced options ]</a></div>').appendTo(row_content);
255
+ adv_link.off('click').on('click', (e) => {row_content.find('.adv').toggle();});
256
+ // 高度なフィルター条件のフォームを表示
257
+ const adv_conditions = ['filter_clmsg_body', 'filter_clmsg_sdate', 'filter_svmsg_edate',
258
+ 'filter_svmsg_id', 'sort', 'offset', 'limit'];
259
+ const adv_row_content = $('<div class="row_content"></div>').appendTo(row_content);
260
+ py_get_cmd_choices.filter(row => adv_conditions.includes(row.opt)).forEach((row, i) => cmdbox.add_form_func(i, form, adv_row_content, row, null, 12, 12));
261
+ adv_row_content.children().each((i, elem) => {$(elem).addClass('adv').hide();}).appendTo(row_content);
262
+ adv_row_content.remove();
263
+ let condition = await cmdbox.load_user_data('audit', 'condition');
264
+ condition = condition && condition['success'] ? JSON.parse(condition['success']) : {};
265
+ Object.keys(condition).forEach((id) => {
266
+ const data = condition[id];
267
+ if (!data) return;
268
+ let elem = row_content.find(`#${id}`);
269
+ if (elem.length > 0) {
270
+ elem.val(data['value']);
271
+ return;
272
+ }
273
+ const last_elem = row_content.find(`[name="${data['name']}"]:last`);
274
+ const add_btn = last_elem.next('.add_buton');
275
+ if (add_btn.length <= 0) return;
276
+ add_btn.click();
277
+ elem = row_content.find(`[name="${data['name']}"]:last`);
278
+ elem.val(val);
279
+ });
280
+ row_content.find(':input').each((i, elem) => {
281
+ const id = $(elem).attr('id');
282
+ if (!id || id == '') return;
283
+ const val = localStorage.getItem(id);
284
+ if (!val || val == '') return
285
+ $(elem).val(val);
286
+ });
287
+ };
288
+ audit.list_audit_metrics = async () => {
289
+ const formData = new FormData();
290
+ const res = await fetch('audit/metrics/list', {method: 'POST', body: formData});
291
+ if (res.status != 200) cmdbox.message({'error':`${res.status}: ${res.statusText}`});
292
+ return await res.json();
293
+ }
294
+ audit.load_audit_metrics = async (title) => {
295
+ const formData = new FormData();
296
+ formData.append('title', title);
297
+ const res = await fetch('audit/metrics/load', {method: 'POST', body: formData});
298
+ if (res.status != 200) cmdbox.message({'error':`${res.status}: ${res.statusText}`});
299
+ return await res.json();
300
+ }
301
+ audit.save_audit_metrics = async (title, opt) => {
302
+ const formData = new FormData();
303
+ formData.append('title', title);
304
+ formData.append('opt', JSON.stringify(opt));
305
+ const res = await fetch('audit/metrics/save', {method: 'POST', body: formData});
306
+ if (res.status != 200) cmdbox.message({'error':`${res.status}: ${res.statusText}`});
307
+ return await res.json();
308
+ };
309
+ audit.del_audit_metrics = async (title) => {
310
+ const formData = new FormData();
311
+ formData.append('title', title);
312
+ const res = await fetch('audit/metrics/delete', {method: 'POST', body: formData});
313
+ if (res.status != 200) cmdbox.message({'error':`${res.status}: ${res.statusText}`});
314
+ return await res.json();
315
+ }
316
+ $(() => {
317
+ // カラーモード対応
318
+ cmdbox.change_color_mode();
319
+ // アイコンを表示
320
+ cmdbox.set_logoicon('.navbar-brand');
321
+ // copyright表示
322
+ cmdbox.copyright();
323
+ // バージョン情報モーダル初期化
324
+ cmdbox.init_version_modal();
325
+ // モーダルボタン初期化
326
+ cmdbox.init_modal_button();
327
+ // コマンド実行用のオプション取得
328
+ cmdbox.get_server_opt(true, $('.filer_form')).then(async (opt) => {
329
+ // フィルターフォーム初期化
330
+ await audit.init_form();
331
+ // 監査ログ一覧表示
332
+ await audit.rawlog();
333
+ // メトリクス表示
334
+ $('#add_metrics').off('click').on('click', (e) => {
335
+ audit.metrics_modal_func();
336
+ });
337
+ });
338
+ // スプリッター初期化
339
+ $('.split-pane').splitPane();
340
+ });
@@ -1,3 +1,7 @@
1
+ .th {
2
+ background-color: var(--bs-tertiary-bg) !important;
3
+ word-break:normal;
4
+ }
1
5
  /* ライトモード (基本) - 必要に応じて調整 */
2
6
  [data-bs-theme="light"] {
3
7
  --bs-body-bg: #fff;