cmdbox 0.5.1.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.
- cmdbox/app/app.py +4 -2
- cmdbox/app/auth/signin.py +634 -631
- cmdbox/app/client.py +10 -10
- cmdbox/app/common.py +50 -6
- cmdbox/app/commons/convert.py +9 -0
- cmdbox/app/commons/module.py +113 -113
- cmdbox/app/commons/redis_client.py +40 -29
- cmdbox/app/edge.py +4 -4
- cmdbox/app/features/cli/audit_base.py +138 -0
- cmdbox/app/features/cli/cmdbox_audit_createdb.py +224 -0
- cmdbox/app/features/cli/cmdbox_audit_delete.py +308 -0
- cmdbox/app/features/cli/cmdbox_audit_search.py +416 -0
- cmdbox/app/features/cli/cmdbox_audit_write.py +247 -0
- cmdbox/app/features/cli/cmdbox_client_file_copy.py +207 -207
- cmdbox/app/features/cli/cmdbox_client_file_download.py +207 -207
- cmdbox/app/features/cli/cmdbox_client_file_list.py +193 -193
- cmdbox/app/features/cli/cmdbox_client_file_mkdir.py +191 -191
- cmdbox/app/features/cli/cmdbox_client_file_move.py +199 -199
- cmdbox/app/features/cli/cmdbox_client_file_remove.py +190 -190
- cmdbox/app/features/cli/cmdbox_client_file_rmdir.py +190 -190
- cmdbox/app/features/cli/cmdbox_client_file_upload.py +212 -212
- cmdbox/app/features/cli/cmdbox_client_server_info.py +166 -166
- cmdbox/app/features/cli/cmdbox_server_list.py +88 -88
- cmdbox/app/features/cli/cmdbox_server_stop.py +138 -138
- cmdbox/app/features/web/cmdbox_web_audit.py +81 -0
- cmdbox/app/features/web/cmdbox_web_audit_metrics.py +72 -0
- cmdbox/app/features/web/cmdbox_web_del_cmd.py +2 -0
- cmdbox/app/features/web/cmdbox_web_del_pipe.py +1 -0
- cmdbox/app/features/web/cmdbox_web_do_signin.py +12 -2
- cmdbox/app/features/web/cmdbox_web_do_signout.py +1 -0
- cmdbox/app/features/web/cmdbox_web_exec_cmd.py +31 -2
- cmdbox/app/features/web/cmdbox_web_exec_pipe.py +1 -0
- cmdbox/app/features/web/cmdbox_web_filer download.py +43 -42
- cmdbox/app/features/web/cmdbox_web_filer.py +1 -0
- cmdbox/app/features/web/cmdbox_web_filer_upload.py +65 -64
- cmdbox/app/features/web/cmdbox_web_gui.py +166 -165
- cmdbox/app/features/web/cmdbox_web_load_pin.py +43 -43
- cmdbox/app/features/web/cmdbox_web_raw_pipe.py +87 -87
- cmdbox/app/features/web/cmdbox_web_save_cmd.py +1 -0
- cmdbox/app/features/web/cmdbox_web_save_pin.py +42 -42
- cmdbox/app/features/web/cmdbox_web_save_pipe.py +1 -0
- cmdbox/app/features/web/cmdbox_web_user_data.py +58 -0
- cmdbox/app/features/web/cmdbox_web_users.py +12 -0
- cmdbox/app/options.py +788 -601
- cmdbox/app/web.py +7 -1
- cmdbox/extensions/features.yml +23 -0
- cmdbox/extensions/sample_project/sample/app/features/cli/sample_client_time.py +82 -82
- cmdbox/extensions/sample_project/sample/app/features/cli/sample_server_time.py +145 -145
- cmdbox/extensions/user_list.yml +5 -0
- cmdbox/licenses/{LICENSE.Sphinx.8.1.3(BSD License).txt → LICENSE.Sphinx.8.2.3(UNKNOWN).txt} +1 -1
- cmdbox/licenses/LICENSE.argcomplete.3.6.2(Apache Software License).txt +177 -0
- cmdbox/licenses/{LICENSE.babel.2.16.0(BSD License).txt → LICENSE.babel.2.17.0(BSD License).txt } +1 -1
- cmdbox/licenses/{LICENSE.pkginfo.1.10.0(MIT License).txt → LICENSE.charset-normalizer.3.4.1(MIT License).txt } +1 -1
- cmdbox/licenses/LICENSE.gevent.25.4.1(MIT).txt +25 -0
- cmdbox/licenses/LICENSE.greenlet.3.2.0(MIT AND Python-2.0).txt +30 -0
- cmdbox/licenses/LICENSE.gunicorn.23.0.0(MIT License).txt +23 -0
- cmdbox/licenses/LICENSE.importlib_metadata.8.6.1(Apache Software License).txt +202 -0
- cmdbox/licenses/LICENSE.nh3.0.2.21(MIT).txt +21 -0
- cmdbox/licenses/{LICENSE.pillow.11.0.0(CMU License (MIT-CMU)).txt → LICENSE.pillow.11.1.0(CMU License (MIT-CMU)).txt } +27 -40
- cmdbox/licenses/LICENSE.pillow.11.2.1(UNKNOWN).txt +1200 -0
- cmdbox/licenses/LICENSE.plyer.2.1.0(MIT License).txt +19 -0
- cmdbox/licenses/LICENSE.prompt_toolkit.3.0.50(BSD License).txt +27 -0
- cmdbox/licenses/LICENSE.prompt_toolkit.3.0.51(BSD License).txt +27 -0
- cmdbox/licenses/LICENSE.psycopg-binary.3.2.6(GNU Lesser General Public License v3 (LGPLv3)).txt +165 -0
- cmdbox/licenses/LICENSE.psycopg-pool.3.2.6(GNU Lesser General Public License v3 (LGPLv3)).txt +165 -0
- cmdbox/licenses/LICENSE.psycopg.3.2.6(GNU Lesser General Public License v3 (LGPLv3)).txt +165 -0
- cmdbox/licenses/LICENSE.pycryptodome.3.22.0(BSD License; Public Domain).txt +61 -0
- cmdbox/licenses/LICENSE.pydantic.2.11.3(MIT License).txt +21 -0
- cmdbox/licenses/LICENSE.pydantic_core.2.33.1(MIT License).txt +21 -0
- cmdbox/licenses/LICENSE.pystray.0.19.5(GNU Lesser General Public License v3 (LGPLv3)).txt +674 -0
- cmdbox/licenses/LICENSE.questionary.2.1.0(MIT License).txt +19 -0
- cmdbox/licenses/LICENSE.roman-numerals-py.3.1.0(CC0 1.0 Universal (CC0 1.0) Public Domain Dedication; Zero-Clause BSD (0BSD)).txt +146 -0
- cmdbox/licenses/{LICENSE.six.1.16.0(MIT License).txt → LICENSE.six.1.17.0(MIT License).txt } +1 -1
- cmdbox/licenses/LICENSE.starlette.0.46.2(BSD License).txt +27 -0
- cmdbox/licenses/{LICENSE.charset-normalizer.3.4.0(MIT License).txt → LICENSE.typing-inspection.0.4.0(MIT License).txt } +2 -2
- cmdbox/licenses/LICENSE.typing_extensions.4.13.2(UNKNOWN).txt +279 -0
- cmdbox/licenses/LICENSE.tzdata.2025.2(Apache Software License).txt +15 -0
- cmdbox/licenses/LICENSE.urllib3.2.4.0(UNKNOWN).txt +21 -0
- cmdbox/licenses/LICENSE.uvicorn.0.34.1(BSD License).txt +27 -0
- cmdbox/licenses/LICENSE.watchfiles.1.0.5(MIT License).txt +21 -0
- cmdbox/licenses/files.txt +49 -38
- cmdbox/logconf_audit.yml +30 -0
- cmdbox/logconf_cmdbox.yml +30 -0
- cmdbox/version.py +2 -2
- cmdbox/web/assets/apexcharts/apexcharts.css +679 -0
- cmdbox/web/assets/apexcharts/apexcharts.min.js +38 -0
- cmdbox/web/assets/cmdbox/audit.js +340 -0
- cmdbox/web/assets/cmdbox/color_mode.css +520 -0
- cmdbox/web/assets/cmdbox/common.js +416 -24
- cmdbox/web/assets/cmdbox/filer_modal.js +1 -1
- cmdbox/web/assets/cmdbox/list_cmd.js +10 -275
- cmdbox/web/assets/cmdbox/list_pipe.js +3 -3
- cmdbox/web/assets/cmdbox/main.js +2 -2
- cmdbox/web/assets/cmdbox/result.js +2 -2
- cmdbox/web/assets/cmdbox/signin.js +2 -2
- cmdbox/web/assets/cmdbox/users.js +19 -20
- cmdbox/web/assets/cmdbox/view_raw.js +1 -1
- cmdbox/web/assets/cmdbox/view_result.js +11 -13
- cmdbox/web/assets/filer/filer.js +2 -2
- cmdbox/web/assets/filer/main.js +2 -2
- cmdbox/web/assets_license_list.txt +4 -1
- cmdbox/web/audit.html +268 -0
- cmdbox/web/filer.html +37 -12
- cmdbox/web/gui.html +36 -53
- cmdbox/web/result.html +24 -3
- cmdbox/web/signin.html +35 -14
- cmdbox/web/users.html +21 -3
- {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.3.dist-info}/METADATA +28 -5
- {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.3.dist-info}/RECORD +142 -103
- {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.3.dist-info}/entry_points.txt +0 -1
- cmdbox/licenses/LICENSE.nh3.0.2.18(MIT).txt +0 -1
- /cmdbox/licenses/{LICENSE.Jinja2.3.1.4(BSD License).txt → LICENSE.Jinja2.3.1.6(BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.Pygments.2.18.0(BSD License).txt → LICENSE.Pygments.2.19.1(BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.anyio.4.6.2.post1(MIT License).txt → LICENSE.anyio.4.9.0(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.argcomplete.3.5.1(Apache Software License).txt → LICENSE.argcomplete.3.6.1(Apache Software License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.certifi.2024.8.30(Mozilla Public License 2.0 (MPL 2.0)).txt → LICENSE.certifi.2025.1.31(Mozilla Public License 2.0 (MPL 2.0)).txt} +0 -0
- /cmdbox/licenses/{LICENSE.click.8.1.7(BSD License).txt → LICENSE.click.8.1.8(BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.cryptography.43.0.3(Apache Software License; BSD License).txt → LICENSE.cryptography.44.0.2(Apache Software License; BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.fastapi.0.115.5(MIT License).txt → LICENSE.fastapi.0.115.12(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.importlib_metadata.8.5.0(Apache Software License).txt → LICENSE.id.1.5.0(Apache Software License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.keyring.25.5.0(MIT License).txt → LICENSE.keyring.25.6.0(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.more-itertools.10.5.0(MIT License).txt → LICENSE.more-itertools.10.6.0(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.numpy.2.1.3(BSD License).txt → LICENSE.numpy.2.2.4(BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.prettytable.3.12.0(BSD License).txt → LICENSE.prettytable.3.16.0(UNKNOWN).txt} +0 -0
- /cmdbox/licenses/{LICENSE.pydantic.2.10.2(MIT License).txt → LICENSE.pydantic.2.11.1(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.pydantic_core.2.27.1(MIT License).txt → LICENSE.pydantic_core.2.33.0(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.python-dotenv.1.0.1(BSD License).txt → LICENSE.python-dotenv.1.1.0(BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.python-multipart.0.0.17(Apache Software License).txt → LICENSE.python-multipart.0.0.20(Apache Software License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.redis.5.2.0(MIT License).txt → LICENSE.redis.5.2.1(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.rich.13.9.4(MIT License).txt → LICENSE.rich.14.0.0(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.sphinx-intl.2.3.0(BSD License).txt → LICENSE.sphinx-intl.2.3.1(BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.starlette.0.41.3(BSD License).txt → LICENSE.starlette.0.46.1(BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.tomli.2.1.0(MIT License).txt → LICENSE.tomli.2.2.1(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.twine.5.1.1(Apache Software License).txt → LICENSE.twine.6.1.0(Apache Software License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.typing_extensions.4.12.2(Python Software Foundation License).txt → LICENSE.typing_extensions.4.13.0(UNKNOWN).txt} +0 -0
- /cmdbox/licenses/{LICENSE.urllib3.2.2.3(MIT License).txt → LICENSE.urllib3.2.3.0(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.uvicorn.0.32.1(BSD License).txt → LICENSE.uvicorn.0.34.0(BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.watchfiles.1.0.0(MIT License).txt → LICENSE.watchfiles.1.0.4(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.websockets.14.1(BSD License).txt → LICENSE.websockets.15.0.1(BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.zope.interface.7.1.1(Zope Public License).txt → LICENSE.zope.interface.7.2(Zope Public License).txt} +0 -0
- {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.3.dist-info}/LICENSE +0 -0
- {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.3.dist-info}/WHEEL +0 -0
- {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.3.dist-info}/top_level.txt +0 -0
|
@@ -1,138 +1,138 @@
|
|
|
1
|
-
from cmdbox.app import common, client, feature
|
|
2
|
-
from cmdbox.app.commons import redis_client
|
|
3
|
-
from cmdbox.app.options import Options
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from typing import Dict, Any, Tuple, List, Union
|
|
6
|
-
import argparse
|
|
7
|
-
import logging
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class ServerStop(feature.OneshotNotifyEdgeFeature):
|
|
11
|
-
def get_mode(self) -> Union[str, List[str]]:
|
|
12
|
-
"""
|
|
13
|
-
この機能のモードを返します
|
|
14
|
-
|
|
15
|
-
Returns:
|
|
16
|
-
Union[str, List[str]]: モード
|
|
17
|
-
"""
|
|
18
|
-
return 'server'
|
|
19
|
-
|
|
20
|
-
def get_cmd(self):
|
|
21
|
-
"""
|
|
22
|
-
この機能のコマンドを返します
|
|
23
|
-
|
|
24
|
-
Returns:
|
|
25
|
-
str: コマンド
|
|
26
|
-
"""
|
|
27
|
-
return 'stop'
|
|
28
|
-
|
|
29
|
-
def get_option(self):
|
|
30
|
-
"""
|
|
31
|
-
この機能のオプションを返します
|
|
32
|
-
|
|
33
|
-
Returns:
|
|
34
|
-
Dict[str, Any]: オプション
|
|
35
|
-
"""
|
|
36
|
-
return dict(
|
|
37
|
-
use_redis=self.USE_REDIS_TRUE, nouse_webmode=True,
|
|
38
|
-
discription_ja="サーバーを停止します。installモードで `cmdbox -m install -c server` を実行している場合は、 `docker-compose down` を使用してください。",
|
|
39
|
-
discription_en="Stop the inference server. If you are running `cmdbox -m install -c server` in install mode, use `docker-compose down`.",
|
|
40
|
-
choice=[
|
|
41
|
-
dict(opt="host", type=Options.T_STR, default=self.default_host, required=True, multi=False, hide=True, choice=None, web="mask",
|
|
42
|
-
discription_ja="Redisサーバーのサービスホストを指定します。",
|
|
43
|
-
discription_en="Specify the service host of the Redis server."),
|
|
44
|
-
dict(opt="port", type=Options.T_INT, default=self.default_port, required=True, multi=False, hide=True, choice=None, web="mask",
|
|
45
|
-
discription_ja="Redisサーバーのサービスポートを指定します。",
|
|
46
|
-
discription_en="Specify the service port of the Redis server."),
|
|
47
|
-
dict(opt="password", type=Options.T_STR, default=self.default_pass, required=True, multi=False, hide=True, choice=None, web="mask",
|
|
48
|
-
discription_ja="Redisサーバーのアクセスパスワード(任意)を指定します。省略時は `password` を使用します。",
|
|
49
|
-
discription_en="Specify the access password of the Redis server (optional). If omitted, `password` is used."),
|
|
50
|
-
dict(opt="svname", type=Options.T_STR, default="server", required=True, multi=False, hide=True, choice=None, web="readonly",
|
|
51
|
-
discription_ja="サーバーのサービス名を指定します。省略時は `server` を使用します。",
|
|
52
|
-
discription_en="Specify the service name of the inference server. If omitted, `server` is used."),
|
|
53
|
-
dict(opt="retry_count", type=Options.T_INT, default=3, required=False, multi=False, hide=True, choice=None,
|
|
54
|
-
discription_ja="Redisサーバーへの再接続回数を指定します。0以下を指定すると永遠に再接続を行います。",
|
|
55
|
-
discription_en="Specifies the number of reconnections to the Redis server.If less than 0 is specified, reconnection is forever."),
|
|
56
|
-
dict(opt="retry_interval", type=Options.T_INT, default=5, required=False, multi=False, hide=True, choice=None,
|
|
57
|
-
discription_ja="Redisサーバーに再接続までの秒数を指定します。",
|
|
58
|
-
discription_en="Specifies the number of seconds before reconnecting to the Redis server."),
|
|
59
|
-
dict(opt="timeout", type=Options.T_INT, default="15", required=False, multi=False, hide=True, choice=None,
|
|
60
|
-
discription_ja="サーバーの応答が返ってくるまでの最大待ち時間を指定。",
|
|
61
|
-
discription_en="Specify the maximum waiting time until the server responds."),
|
|
62
|
-
dict(opt="output_json", short="o", type=Options.T_FILE, default=None, required=False, multi=False, hide=True, choice=None, fileio="out",
|
|
63
|
-
discription_ja="処理結果jsonの保存先ファイルを指定。",
|
|
64
|
-
discription_en="Specify the destination file for saving the processing result json."),
|
|
65
|
-
dict(opt="output_json_append", short="a", type=Options.T_BOOL, default=False, required=False, multi=False, hide=True, choice=[True, False],
|
|
66
|
-
discription_ja="処理結果jsonファイルを追記保存します。",
|
|
67
|
-
discription_en="Save the processing result json file by appending."),
|
|
68
|
-
dict(opt="stdout_log", type=Options.T_BOOL, default=True, required=False, multi=False, hide=True, choice=[True, False],
|
|
69
|
-
discription_ja="GUIモードでのみ使用可能です。コマンド実行時の標準出力をConsole logに出力します。",
|
|
70
|
-
discription_en="Available only in GUI mode. Outputs standard output during command execution to Console log."),
|
|
71
|
-
dict(opt="capture_stdout", type=Options.T_BOOL, default=True, required=False, multi=False, hide=True, choice=[True, False],
|
|
72
|
-
discription_ja="GUIモードでのみ使用可能です。コマンド実行時の標準出力をキャプチャーし、実行結果画面に表示します。",
|
|
73
|
-
discription_en="Available only in GUI mode. Captures standard output during command execution and displays it on the execution result screen."),
|
|
74
|
-
dict(opt="capture_maxsize", type=Options.T_INT, default=self.DEFAULT_CAPTURE_MAXSIZE, required=False, multi=False, hide=True, choice=None,
|
|
75
|
-
discription_ja="GUIモードでのみ使用可能です。コマンド実行時の標準出力の最大キャプチャーサイズを指定します。",
|
|
76
|
-
discription_en="Available only in GUI mode. Specifies the maximum capture size of standard output when executing commands."),
|
|
77
|
-
]
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
def get_svcmd(self):
|
|
81
|
-
"""
|
|
82
|
-
この機能のサーバー側のコマンドを返します
|
|
83
|
-
|
|
84
|
-
Returns:
|
|
85
|
-
str: サーバー側のコマンド
|
|
86
|
-
"""
|
|
87
|
-
return 'stop_server'
|
|
88
|
-
|
|
89
|
-
def apprun(self, logger:logging.Logger, args:argparse.Namespace, tm:float, pf:List[Dict[str, float]]=[]) -> Tuple[int, Dict[str, Any], Any]:
|
|
90
|
-
"""
|
|
91
|
-
この機能の実行を行います
|
|
92
|
-
|
|
93
|
-
Args:
|
|
94
|
-
logger (logging.Logger): ロガー
|
|
95
|
-
args (argparse.Namespace): 引数
|
|
96
|
-
tm (float): 実行開始時間
|
|
97
|
-
pf (List[Dict[str, float]]): 呼出元のパフォーマンス情報
|
|
98
|
-
|
|
99
|
-
Returns:
|
|
100
|
-
Tuple[int, Dict[str, Any], Any]: 終了コード, 結果, オブジェクト
|
|
101
|
-
"""
|
|
102
|
-
if args.svname is None:
|
|
103
|
-
msg = dict(warn=f"Please specify the --svname option.")
|
|
104
|
-
common.print_format(msg, args.format, tm, args.output_json, args.output_json_append, pf=pf)
|
|
105
|
-
return 1, msg
|
|
106
|
-
cl = client.Client(logger, redis_host=args.host, redis_port=args.port, redis_password=args.password, svname=args.svname)
|
|
107
|
-
ret = cl.stop_server(retry_count=args.retry_count, retry_interval=args.retry_interval, timeout=args.timeout)
|
|
108
|
-
common.print_format(ret, args.format, tm, args.output_json, args.output_json_append, pf=pf)
|
|
109
|
-
if 'success' not in ret:
|
|
110
|
-
return 1, ret, cl
|
|
111
|
-
return 0, ret, cl
|
|
112
|
-
|
|
113
|
-
def is_cluster_redirect(self):
|
|
114
|
-
"""
|
|
115
|
-
クラスター宛のメッセージの場合、メッセージを転送するかどうかを返します
|
|
116
|
-
|
|
117
|
-
Returns:
|
|
118
|
-
bool: メッセージを転送する場合はTrue
|
|
119
|
-
"""
|
|
120
|
-
return True
|
|
121
|
-
|
|
122
|
-
def svrun(self, data_dir:Path, logger:logging.Logger, redis_cli:redis_client.RedisClient, msg:List[str],
|
|
123
|
-
sessions:Dict[str, Dict[str, Any]]) -> int:
|
|
124
|
-
"""
|
|
125
|
-
この機能のサーバー側の実行を行います
|
|
126
|
-
|
|
127
|
-
Args:
|
|
128
|
-
data_dir (Path): データディレクトリ
|
|
129
|
-
logger (logging.Logger): ロガー
|
|
130
|
-
redis_cli (redis_client.RedisClient): Redisクライアント
|
|
131
|
-
msg (List[str]): 受信メッセージ
|
|
132
|
-
sessions (Dict[str, Dict[str, Any]]): セッション情報
|
|
133
|
-
|
|
134
|
-
Returns:
|
|
135
|
-
int: 終了コード
|
|
136
|
-
"""
|
|
137
|
-
redis_cli.rpush(msg[1], dict(success=f"Successful stop server. svname={redis_cli.svname}"))
|
|
138
|
-
return self.RESP_SCCESS
|
|
1
|
+
from cmdbox.app import common, client, feature
|
|
2
|
+
from cmdbox.app.commons import redis_client
|
|
3
|
+
from cmdbox.app.options import Options
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Dict, Any, Tuple, List, Union
|
|
6
|
+
import argparse
|
|
7
|
+
import logging
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ServerStop(feature.OneshotNotifyEdgeFeature):
|
|
11
|
+
def get_mode(self) -> Union[str, List[str]]:
|
|
12
|
+
"""
|
|
13
|
+
この機能のモードを返します
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
Union[str, List[str]]: モード
|
|
17
|
+
"""
|
|
18
|
+
return 'server'
|
|
19
|
+
|
|
20
|
+
def get_cmd(self):
|
|
21
|
+
"""
|
|
22
|
+
この機能のコマンドを返します
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
str: コマンド
|
|
26
|
+
"""
|
|
27
|
+
return 'stop'
|
|
28
|
+
|
|
29
|
+
def get_option(self):
|
|
30
|
+
"""
|
|
31
|
+
この機能のオプションを返します
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Dict[str, Any]: オプション
|
|
35
|
+
"""
|
|
36
|
+
return dict(
|
|
37
|
+
use_redis=self.USE_REDIS_TRUE, nouse_webmode=True,
|
|
38
|
+
discription_ja="サーバーを停止します。installモードで `cmdbox -m install -c server` を実行している場合は、 `docker-compose down` を使用してください。",
|
|
39
|
+
discription_en="Stop the inference server. If you are running `cmdbox -m install -c server` in install mode, use `docker-compose down`.",
|
|
40
|
+
choice=[
|
|
41
|
+
dict(opt="host", type=Options.T_STR, default=self.default_host, required=True, multi=False, hide=True, choice=None, web="mask",
|
|
42
|
+
discription_ja="Redisサーバーのサービスホストを指定します。",
|
|
43
|
+
discription_en="Specify the service host of the Redis server."),
|
|
44
|
+
dict(opt="port", type=Options.T_INT, default=self.default_port, required=True, multi=False, hide=True, choice=None, web="mask",
|
|
45
|
+
discription_ja="Redisサーバーのサービスポートを指定します。",
|
|
46
|
+
discription_en="Specify the service port of the Redis server."),
|
|
47
|
+
dict(opt="password", type=Options.T_STR, default=self.default_pass, required=True, multi=False, hide=True, choice=None, web="mask",
|
|
48
|
+
discription_ja="Redisサーバーのアクセスパスワード(任意)を指定します。省略時は `password` を使用します。",
|
|
49
|
+
discription_en="Specify the access password of the Redis server (optional). If omitted, `password` is used."),
|
|
50
|
+
dict(opt="svname", type=Options.T_STR, default="server", required=True, multi=False, hide=True, choice=None, web="readonly",
|
|
51
|
+
discription_ja="サーバーのサービス名を指定します。省略時は `server` を使用します。",
|
|
52
|
+
discription_en="Specify the service name of the inference server. If omitted, `server` is used."),
|
|
53
|
+
dict(opt="retry_count", type=Options.T_INT, default=3, required=False, multi=False, hide=True, choice=None,
|
|
54
|
+
discription_ja="Redisサーバーへの再接続回数を指定します。0以下を指定すると永遠に再接続を行います。",
|
|
55
|
+
discription_en="Specifies the number of reconnections to the Redis server.If less than 0 is specified, reconnection is forever."),
|
|
56
|
+
dict(opt="retry_interval", type=Options.T_INT, default=5, required=False, multi=False, hide=True, choice=None,
|
|
57
|
+
discription_ja="Redisサーバーに再接続までの秒数を指定します。",
|
|
58
|
+
discription_en="Specifies the number of seconds before reconnecting to the Redis server."),
|
|
59
|
+
dict(opt="timeout", type=Options.T_INT, default="15", required=False, multi=False, hide=True, choice=None,
|
|
60
|
+
discription_ja="サーバーの応答が返ってくるまでの最大待ち時間を指定。",
|
|
61
|
+
discription_en="Specify the maximum waiting time until the server responds."),
|
|
62
|
+
dict(opt="output_json", short="o", type=Options.T_FILE, default=None, required=False, multi=False, hide=True, choice=None, fileio="out",
|
|
63
|
+
discription_ja="処理結果jsonの保存先ファイルを指定。",
|
|
64
|
+
discription_en="Specify the destination file for saving the processing result json."),
|
|
65
|
+
dict(opt="output_json_append", short="a", type=Options.T_BOOL, default=False, required=False, multi=False, hide=True, choice=[True, False],
|
|
66
|
+
discription_ja="処理結果jsonファイルを追記保存します。",
|
|
67
|
+
discription_en="Save the processing result json file by appending."),
|
|
68
|
+
dict(opt="stdout_log", type=Options.T_BOOL, default=True, required=False, multi=False, hide=True, choice=[True, False],
|
|
69
|
+
discription_ja="GUIモードでのみ使用可能です。コマンド実行時の標準出力をConsole logに出力します。",
|
|
70
|
+
discription_en="Available only in GUI mode. Outputs standard output during command execution to Console log."),
|
|
71
|
+
dict(opt="capture_stdout", type=Options.T_BOOL, default=True, required=False, multi=False, hide=True, choice=[True, False],
|
|
72
|
+
discription_ja="GUIモードでのみ使用可能です。コマンド実行時の標準出力をキャプチャーし、実行結果画面に表示します。",
|
|
73
|
+
discription_en="Available only in GUI mode. Captures standard output during command execution and displays it on the execution result screen."),
|
|
74
|
+
dict(opt="capture_maxsize", type=Options.T_INT, default=self.DEFAULT_CAPTURE_MAXSIZE, required=False, multi=False, hide=True, choice=None,
|
|
75
|
+
discription_ja="GUIモードでのみ使用可能です。コマンド実行時の標準出力の最大キャプチャーサイズを指定します。",
|
|
76
|
+
discription_en="Available only in GUI mode. Specifies the maximum capture size of standard output when executing commands."),
|
|
77
|
+
]
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
def get_svcmd(self):
|
|
81
|
+
"""
|
|
82
|
+
この機能のサーバー側のコマンドを返します
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
str: サーバー側のコマンド
|
|
86
|
+
"""
|
|
87
|
+
return 'stop_server'
|
|
88
|
+
|
|
89
|
+
def apprun(self, logger:logging.Logger, args:argparse.Namespace, tm:float, pf:List[Dict[str, float]]=[]) -> Tuple[int, Dict[str, Any], Any]:
|
|
90
|
+
"""
|
|
91
|
+
この機能の実行を行います
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
logger (logging.Logger): ロガー
|
|
95
|
+
args (argparse.Namespace): 引数
|
|
96
|
+
tm (float): 実行開始時間
|
|
97
|
+
pf (List[Dict[str, float]]): 呼出元のパフォーマンス情報
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Tuple[int, Dict[str, Any], Any]: 終了コード, 結果, オブジェクト
|
|
101
|
+
"""
|
|
102
|
+
if args.svname is None:
|
|
103
|
+
msg = dict(warn=f"Please specify the --svname option.")
|
|
104
|
+
common.print_format(msg, args.format, tm, args.output_json, args.output_json_append, pf=pf)
|
|
105
|
+
return 1, msg
|
|
106
|
+
cl = client.Client(logger, redis_host=args.host, redis_port=args.port, redis_password=args.password, svname=args.svname)
|
|
107
|
+
ret = cl.stop_server(retry_count=args.retry_count, retry_interval=args.retry_interval, timeout=args.timeout)
|
|
108
|
+
common.print_format(ret, args.format, tm, args.output_json, args.output_json_append, pf=pf)
|
|
109
|
+
if 'success' not in ret:
|
|
110
|
+
return 1, ret, cl
|
|
111
|
+
return 0, ret, cl
|
|
112
|
+
|
|
113
|
+
def is_cluster_redirect(self):
|
|
114
|
+
"""
|
|
115
|
+
クラスター宛のメッセージの場合、メッセージを転送するかどうかを返します
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
bool: メッセージを転送する場合はTrue
|
|
119
|
+
"""
|
|
120
|
+
return True
|
|
121
|
+
|
|
122
|
+
def svrun(self, data_dir:Path, logger:logging.Logger, redis_cli:redis_client.RedisClient, msg:List[str],
|
|
123
|
+
sessions:Dict[str, Dict[str, Any]]) -> int:
|
|
124
|
+
"""
|
|
125
|
+
この機能のサーバー側の実行を行います
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
data_dir (Path): データディレクトリ
|
|
129
|
+
logger (logging.Logger): ロガー
|
|
130
|
+
redis_cli (redis_client.RedisClient): Redisクライアント
|
|
131
|
+
msg (List[str]): 受信メッセージ
|
|
132
|
+
sessions (Dict[str, Dict[str, Any]]): セッション情報
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
int: 終了コード
|
|
136
|
+
"""
|
|
137
|
+
redis_cli.rpush(msg[1], dict(success=f"Successful stop server. svname={redis_cli.svname}"))
|
|
138
|
+
return self.RESP_SCCESS
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from cmdbox.app import common, feature
|
|
2
|
+
from cmdbox.app.web import Web
|
|
3
|
+
from fastapi import FastAPI, Request, Response
|
|
4
|
+
from fastapi.responses import HTMLResponse
|
|
5
|
+
from typing import Dict, Any
|
|
6
|
+
import argparse
|
|
7
|
+
import time
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Audit(feature.WebFeature):
|
|
11
|
+
|
|
12
|
+
def route(self, web:Web, app:FastAPI) -> None:
|
|
13
|
+
"""
|
|
14
|
+
webモードのルーティングを設定します
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
web (Web): Webオブジェクト
|
|
18
|
+
app (FastAPI): FastAPIオブジェクト
|
|
19
|
+
"""
|
|
20
|
+
if web.audit_html is not None:
|
|
21
|
+
if not web.audit_html.is_file():
|
|
22
|
+
raise FileNotFoundError(f'audit_html is not found. ({web.audit_html})')
|
|
23
|
+
with open(web.audit_html, 'r', encoding='utf-8') as f:
|
|
24
|
+
web.audit_html_data = f.read()
|
|
25
|
+
|
|
26
|
+
@app.get('/audit', response_class=HTMLResponse)
|
|
27
|
+
@app.post('/audit', response_class=HTMLResponse)
|
|
28
|
+
async def audit(req:Request, res:Response):
|
|
29
|
+
signin = web.signin.check_signin(req, res)
|
|
30
|
+
if signin is not None:
|
|
31
|
+
return signin
|
|
32
|
+
res.headers['Access-Control-Allow-Origin'] = '*'
|
|
33
|
+
web.options.audit_exec(req, res, web)
|
|
34
|
+
return web.audit_html_data
|
|
35
|
+
|
|
36
|
+
@app.post('/audit/rawlog')
|
|
37
|
+
async def audit_rawlog(req:Request, res:Response):
|
|
38
|
+
signin = web.signin.check_signin(req, res)
|
|
39
|
+
if signin is not None:
|
|
40
|
+
return signin
|
|
41
|
+
if web.signin.get_data() is None:
|
|
42
|
+
return dict(error='signin_file_data is None.')
|
|
43
|
+
if not hasattr(web.options, 'audit_search') or web.options.audit_search is None:
|
|
44
|
+
raise dict(error='audit search feature is not found.')
|
|
45
|
+
opt = await req.json()
|
|
46
|
+
opt = {**opt, **web.options.audit_search_args.copy()}
|
|
47
|
+
args = argparse.Namespace(**{k:common.chopdq(v) for k,v in opt.items()})
|
|
48
|
+
status, ret_main, _ = web.options.audit_search.apprun(web.logger, args, time.perf_counter(), [])
|
|
49
|
+
if status != 0:
|
|
50
|
+
return dict(error=ret_main)
|
|
51
|
+
return ret_main
|
|
52
|
+
|
|
53
|
+
@app.get('/audit/mode_cmd')
|
|
54
|
+
async def audit_mode_cmd(req:Request, res:Response):
|
|
55
|
+
signin = web.signin.check_signin(req, res)
|
|
56
|
+
if signin is not None:
|
|
57
|
+
return signin
|
|
58
|
+
return dict(success=web.options.audit_search_args)
|
|
59
|
+
|
|
60
|
+
def toolmenu(self, web:Web) -> Dict[str, Any]:
|
|
61
|
+
"""
|
|
62
|
+
ツールメニューの情報を返します
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
web (Web): Webオブジェクト
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Dict[str, Any]: ツールメニュー情報
|
|
69
|
+
|
|
70
|
+
Sample:
|
|
71
|
+
{
|
|
72
|
+
'filer': {
|
|
73
|
+
'html': 'Filer',
|
|
74
|
+
'href': 'filer',
|
|
75
|
+
'target': '_blank',
|
|
76
|
+
'css_class': 'dropdown-item'
|
|
77
|
+
'onclick': 'alert("filer")'
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
"""
|
|
81
|
+
return dict(audit=dict(html='Audit', href='audit', target='_blank', css_class='dropdown-item'))
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from cmdbox.app import common, feature
|
|
2
|
+
from cmdbox.app.web import Web
|
|
3
|
+
from fastapi import FastAPI, Request, Response, HTTPException
|
|
4
|
+
from typing import Dict, Any
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AuditMetrics(feature.WebFeature):
|
|
9
|
+
def route(self, web:Web, app:FastAPI) -> None:
|
|
10
|
+
"""
|
|
11
|
+
webモードのルーティングを設定します
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
web (Web): Webオブジェクト
|
|
15
|
+
app (FastAPI): FastAPIオブジェクト
|
|
16
|
+
"""
|
|
17
|
+
@app.post('/audit/metrics/save')
|
|
18
|
+
async def save_metrics(req:Request, res:Response):
|
|
19
|
+
signin = web.signin.check_signin(req, res)
|
|
20
|
+
if signin is not None:
|
|
21
|
+
raise HTTPException(status_code=401, detail=self.DEFAULT_401_MESSAGE)
|
|
22
|
+
form = await req.form()
|
|
23
|
+
title = form.get('title')
|
|
24
|
+
opt = json.loads(form.get('opt'))
|
|
25
|
+
if common.check_fname(title):
|
|
26
|
+
return dict(warn=f'The title contains invalid characters."{title}"')
|
|
27
|
+
opt_path = web.audit_path / f"metrics-{title}.json"
|
|
28
|
+
web.logger.info(f"save_metrics: opt_path={opt_path}, opt={opt}")
|
|
29
|
+
common.saveopt(opt, opt_path, True)
|
|
30
|
+
ret = dict(success=f'Metrics "{title}" saved in "{opt_path}".')
|
|
31
|
+
web.options.audit_exec(req, res, web, title=title)
|
|
32
|
+
return ret
|
|
33
|
+
|
|
34
|
+
@app.post('/audit/metrics/load')
|
|
35
|
+
async def load_metrics(req:Request, res:Response):
|
|
36
|
+
signin = web.signin.check_signin(req, res)
|
|
37
|
+
if signin is not None:
|
|
38
|
+
raise HTTPException(status_code=401, detail=self.DEFAULT_401_MESSAGE)
|
|
39
|
+
form = await req.form()
|
|
40
|
+
title = form.get('title')
|
|
41
|
+
opt_path = web.audit_path / f"metrics-{title}.json"
|
|
42
|
+
if not opt_path.is_file():
|
|
43
|
+
return dict(warn=f'The metrics file is not found."{opt_path}"')
|
|
44
|
+
with open(opt_path, 'r', encoding='utf-8') as f:
|
|
45
|
+
opt = json.load(f)
|
|
46
|
+
return dict(success=opt)
|
|
47
|
+
|
|
48
|
+
@app.post('/audit/metrics/delete')
|
|
49
|
+
async def delete_metrics(req:Request, res:Response):
|
|
50
|
+
signin = web.signin.check_signin(req, res)
|
|
51
|
+
if signin is not None:
|
|
52
|
+
raise HTTPException(status_code=401, detail=self.DEFAULT_401_MESSAGE)
|
|
53
|
+
form = await req.form()
|
|
54
|
+
title = form.get('title')
|
|
55
|
+
opt_path = web.audit_path / f"metrics-{title}.json"
|
|
56
|
+
if not opt_path.is_file():
|
|
57
|
+
return dict(warn=f'The metrics file is not found."{opt_path}"')
|
|
58
|
+
opt_path.unlink()
|
|
59
|
+
return dict(success=f'Metrics "{title}" deleted.')
|
|
60
|
+
|
|
61
|
+
@app.post('/audit/metrics/list')
|
|
62
|
+
async def list_metrics(req:Request, res:Response):
|
|
63
|
+
signin = web.signin.check_signin(req, res)
|
|
64
|
+
if signin is not None:
|
|
65
|
+
raise HTTPException(status_code=401, detail=self.DEFAULT_401_MESSAGE)
|
|
66
|
+
files = web.audit_path.glob('metrics-*.json')
|
|
67
|
+
ret = []
|
|
68
|
+
for f in files:
|
|
69
|
+
with open(f, 'r', encoding='utf-8') as f:
|
|
70
|
+
opt = json.load(f)
|
|
71
|
+
ret.append(opt)
|
|
72
|
+
return dict(success=ret)
|
|
@@ -17,6 +17,7 @@ class DelCmd(feature.WebFeature):
|
|
|
17
17
|
signin = web.signin.check_signin(req, res)
|
|
18
18
|
if signin is not None:
|
|
19
19
|
raise HTTPException(status_code=401, detail=self.DEFAULT_401_MESSAGE)
|
|
20
|
+
|
|
20
21
|
form = await req.form()
|
|
21
22
|
title = form.get('title')
|
|
22
23
|
|
|
@@ -26,4 +27,5 @@ class DelCmd(feature.WebFeature):
|
|
|
26
27
|
if 'signin' in req.session and req.session['signin'] is not None:
|
|
27
28
|
sess = req.session['signin']
|
|
28
29
|
web.user_data(req, sess['uid'], sess['name'], 'cmdpins', title, delkey=True)
|
|
30
|
+
web.options.audit_exec(req, res, web)
|
|
29
31
|
return {}
|
|
@@ -26,5 +26,6 @@ class DelPipe(feature.WebFeature):
|
|
|
26
26
|
if 'signin' in req.session and req.session['signin'] is not None:
|
|
27
27
|
sess = req.session['signin']
|
|
28
28
|
web.user_data(req, sess['uid'], sess['name'], 'pipepins', title, delkey=True)
|
|
29
|
+
web.options.audit_exec(req, res, web)
|
|
29
30
|
return {}
|
|
30
31
|
|
|
@@ -58,9 +58,11 @@ class DoSignin(cmdbox_web_signin.Signin):
|
|
|
58
58
|
pass
|
|
59
59
|
if not token_ok:
|
|
60
60
|
if name == '' or passwd == '':
|
|
61
|
+
web.options.audit_exec(req, res, web, body=dict(msg='signin failed.'), audit_type='auth')
|
|
61
62
|
return RedirectResponse(url=f'/signin/{next}?error=1')
|
|
62
63
|
user = [u for u in signin_data['users'] if u['name'] == name and u['hash'] != 'oauth2']
|
|
63
64
|
if len(user) <= 0:
|
|
65
|
+
web.options.audit_exec(req, res, web, body=dict(msg='signin failed.'), audit_type='auth')
|
|
64
66
|
return RedirectResponse(url=f'/signin/{next}?error=1')
|
|
65
67
|
user = user[0]
|
|
66
68
|
if web.logger.level == logging.DEBUG:
|
|
@@ -84,6 +86,7 @@ class DoSignin(cmdbox_web_signin.Signin):
|
|
|
84
86
|
if pass_miss_count >= threshold:
|
|
85
87
|
# ロックアウト
|
|
86
88
|
web.user_data(None, uid, name, 'password', 'pass_miss_count', )
|
|
89
|
+
web.options.audit_exec(req, res, web, body=dict(msg='Accound lockout.'), audit_type='auth', user=name)
|
|
87
90
|
return RedirectResponse(url=f'/signin/{next}?error=lockout')
|
|
88
91
|
|
|
89
92
|
if not token_ok:
|
|
@@ -96,6 +99,7 @@ class DoSignin(cmdbox_web_signin.Signin):
|
|
|
96
99
|
web.user_data(None, uid, name, 'password', 'pass_miss_last', datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S'))
|
|
97
100
|
web.user_data(None, uid, name, 'password', 'pass_miss_count', pass_miss_count+1)
|
|
98
101
|
web.logger.warning(f'Failed to signin. name={name}, pass_miss_count={pass_miss_count+1}')
|
|
102
|
+
web.options.audit_exec(req, res, web, body=dict(msg='Wrong password.'), audit_type='auth', user=name)
|
|
99
103
|
return RedirectResponse(url=f'/signin/{next}?error=1')
|
|
100
104
|
group_names = list(set(web.signin.__class__.correct_group(signin_data, user['groups'], None)))
|
|
101
105
|
gids = [g['gid'] for g in signin_data['groups'] if g['name'] in group_names]
|
|
@@ -113,16 +117,22 @@ class DoSignin(cmdbox_web_signin.Signin):
|
|
|
113
117
|
notify = expiration['notify']
|
|
114
118
|
# パスワード有効期限チェック
|
|
115
119
|
if datetime.datetime.now() > last_update + datetime.timedelta(days=period):
|
|
120
|
+
web.options.audit_exec(req, res, web, body=dict(msg='Password is expired.'), audit_type='auth', user=name)
|
|
116
121
|
return RedirectResponse(url=f'/signin/{next}?error=expirationofpassword')
|
|
117
122
|
if datetime.datetime.now() > last_update + datetime.timedelta(days=notify):
|
|
118
123
|
# セッションに保存
|
|
119
124
|
_set_session(req, dict(uid=uid, name=name), email, passwd, None, group_names, gids)
|
|
120
125
|
next = f"../{next}" if token_ok else next
|
|
121
|
-
|
|
126
|
+
web.options.audit_exec(req, res, web, body=dict(msg='Signin succeeded. However, you should change your password.'), audit_type='auth', user=name)
|
|
127
|
+
return RedirectResponse(url=f'../{next}?warn=passchange', headers=dict(signin="success"))
|
|
122
128
|
# セッションに保存
|
|
123
129
|
_set_session(req, dict(uid=uid, name=name), email, passwd, None, group_names, gids)
|
|
124
130
|
next = f"../{next}" if token_ok else next
|
|
125
|
-
|
|
131
|
+
if notify_passchange:
|
|
132
|
+
web.options.audit_exec(req, res, web, body=dict(msg='Signin succeeded. However, you should change your password.'), audit_type='auth', user=name)
|
|
133
|
+
return RedirectResponse(url=f'../{next}?warn=passchange', headers=dict(signin="success"))
|
|
134
|
+
web.options.audit_exec(req, res, web, body=dict(msg='Signin succeeded.'), audit_type='auth', user=name)
|
|
135
|
+
return RedirectResponse(url=f'../{next}', headers=dict(signin="success"))
|
|
126
136
|
|
|
127
137
|
def _load_signin(web:Web, signin_module:str, appcls, ver):
|
|
128
138
|
"""
|
|
@@ -17,6 +17,7 @@ class DoSignout(feature.WebFeature):
|
|
|
17
17
|
@app.post('/dosignout/{next}', response_class=HTMLResponse)
|
|
18
18
|
async def do_signout(next, req:Request, res:Response):
|
|
19
19
|
if 'signin' in req.session:
|
|
20
|
+
web.options.audit_exec(req, res, web, body=dict(msg='Signout.'), audit_type='auth')
|
|
20
21
|
for key in list(req.session.keys()).copy():
|
|
21
22
|
del req.session[key]
|
|
22
23
|
return RedirectResponse(url=f'../signin/{next}') # nginxのリバプロ対応のための相対パス
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
from cmdbox.app import app, client, options, server, web as _web
|
|
1
|
+
from cmdbox.app import app, client, common, options, server, web as _web
|
|
2
2
|
from cmdbox.app.commons import convert, loghandler
|
|
3
|
+
from cmdbox.app.features.cli import cmdbox_audit_search, cmdbox_audit_write
|
|
3
4
|
from cmdbox.app.features.web import cmdbox_web_load_cmd
|
|
4
5
|
from cmdbox.app.web import Web
|
|
5
6
|
from fastapi import FastAPI, Request, Response, HTTPException
|
|
@@ -10,6 +11,7 @@ import io
|
|
|
10
11
|
import json
|
|
11
12
|
import traceback
|
|
12
13
|
import sys
|
|
14
|
+
import uuid
|
|
13
15
|
|
|
14
16
|
|
|
15
17
|
class ExecCmd(cmdbox_web_load_cmd.LoadCmd):
|
|
@@ -110,16 +112,43 @@ class ExecCmd(cmdbox_web_load_cmd.LoadCmd):
|
|
|
110
112
|
Returns:
|
|
111
113
|
list: コマンド実行結果
|
|
112
114
|
"""
|
|
115
|
+
tags = []
|
|
116
|
+
if 'tag' in opt and isinstance(opt['tag'], list):
|
|
117
|
+
tags = [t for t in opt['tag'] if t is not None and t != '']
|
|
118
|
+
web.options.audit_exec(req, res, web, tags=tags, title=title)
|
|
113
119
|
appcls = self.appcls if appcls is None else appcls
|
|
114
120
|
appcls = app.CmdBoxApp if appcls is None else appcls
|
|
115
121
|
web.container['cmdbox_app'] = ap = appcls.getInstance(appcls=appcls, ver=self.ver)
|
|
116
122
|
if 'mode' in opt and 'cmd' in opt:
|
|
117
123
|
if not web.signin.check_cmd(req, res, opt['mode'], opt['cmd']):
|
|
118
124
|
return dict(warn=f'Command "{title}" failed. Execute command denyed. mode={opt["mode"]}, cmd={opt["cmd"]}')
|
|
125
|
+
_options = options.Options.getInstance()
|
|
126
|
+
schema = _options.get_cmd_choices(opt['mode'], opt['cmd'], False)
|
|
127
|
+
try:
|
|
128
|
+
opt_path = web.cmds_path / f"cmd-{title}.json"
|
|
129
|
+
feat = _options.get_cmd_attr(opt['mode'], opt['cmd'], "feature")
|
|
130
|
+
loaded = common.loadopt(opt_path, False)
|
|
131
|
+
for o in opt.keys():
|
|
132
|
+
found = False
|
|
133
|
+
for s in schema:
|
|
134
|
+
if 'opt' not in s or s['opt'] != o: continue
|
|
135
|
+
if 'web' not in s or s['web'] != 'mask': continue
|
|
136
|
+
found = True
|
|
137
|
+
if not found or o not in loaded: continue
|
|
138
|
+
opt[o] = loaded[o]
|
|
139
|
+
if isinstance(feat, cmdbox_audit_write.AuditWrite) and o in _options.audit_write_args:
|
|
140
|
+
opt[o] = _options.audit_write_args[o]
|
|
141
|
+
elif isinstance(feat, cmdbox_audit_search.AuditSearch) and o in _options.audit_search_args:
|
|
142
|
+
opt[o] = _options.audit_search_args[o]
|
|
143
|
+
except:
|
|
144
|
+
pass
|
|
119
145
|
if 'host' in opt: opt['host'] = web.redis_host
|
|
120
146
|
if 'port' in opt: opt['port'] = web.redis_port
|
|
121
147
|
if 'password' in opt: opt['password'] = web.redis_password
|
|
122
148
|
if 'svname' in opt: opt['svname'] = web.svname
|
|
149
|
+
if req.session is not None and 'signin' in req.session and req.session['signin'] is not None:
|
|
150
|
+
if 'clmsg_id' in req.session['signin'] and req.session['signin']['clmsg_id'] is not None:
|
|
151
|
+
opt['clmsg_id'] = req.session['signin']['clmsg_id']
|
|
123
152
|
ap.sv = None
|
|
124
153
|
ap.cl = None
|
|
125
154
|
ap.web = None
|
|
@@ -151,7 +180,7 @@ class ExecCmd(cmdbox_web_load_cmd.LoadCmd):
|
|
|
151
180
|
logsize = 1024
|
|
152
181
|
try:
|
|
153
182
|
old_stdout.write(loghandler.colorize_msg(f'EXEC: {opt_list}\n'[:logsize]))
|
|
154
|
-
status, ret_main, obj = cmdbox_app.main(args_list=opt_list, file_dict=file_dict, webcall=True)
|
|
183
|
+
status, ret_main, obj = cmdbox_app.main(args_list=[common.chopdq(o) for o in opt_list], file_dict=file_dict, webcall=True)
|
|
155
184
|
if isinstance(obj, server.Server):
|
|
156
185
|
cmdbox_app.sv = obj
|
|
157
186
|
elif isinstance(obj, client.Client):
|
|
@@ -84,6 +84,7 @@ class ExecPipe(cmdbox_web_load_pipe.LoadPipe, cmdbox_web_raw_pipe.RawPipe):
|
|
|
84
84
|
for tfname in upfiles.values():
|
|
85
85
|
os.unlink(tfname)
|
|
86
86
|
|
|
87
|
+
@options.Options.audit()
|
|
87
88
|
def exec_pipe(self, req:Request, res:Response, web:Web,
|
|
88
89
|
title:str, opt:Dict[str, Any], nothread:bool=False, capture_stdin:bool=False) -> List[Dict[str, Any]]:
|
|
89
90
|
"""
|