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.

Files changed (143) hide show
  1. cmdbox/app/app.py +4 -2
  2. cmdbox/app/auth/signin.py +634 -631
  3. cmdbox/app/client.py +10 -10
  4. cmdbox/app/common.py +50 -6
  5. cmdbox/app/commons/convert.py +9 -0
  6. cmdbox/app/commons/module.py +113 -113
  7. cmdbox/app/commons/redis_client.py +40 -29
  8. cmdbox/app/edge.py +4 -4
  9. cmdbox/app/features/cli/audit_base.py +138 -0
  10. cmdbox/app/features/cli/cmdbox_audit_createdb.py +224 -0
  11. cmdbox/app/features/cli/cmdbox_audit_delete.py +308 -0
  12. cmdbox/app/features/cli/cmdbox_audit_search.py +416 -0
  13. cmdbox/app/features/cli/cmdbox_audit_write.py +247 -0
  14. cmdbox/app/features/cli/cmdbox_client_file_copy.py +207 -207
  15. cmdbox/app/features/cli/cmdbox_client_file_download.py +207 -207
  16. cmdbox/app/features/cli/cmdbox_client_file_list.py +193 -193
  17. cmdbox/app/features/cli/cmdbox_client_file_mkdir.py +191 -191
  18. cmdbox/app/features/cli/cmdbox_client_file_move.py +199 -199
  19. cmdbox/app/features/cli/cmdbox_client_file_remove.py +190 -190
  20. cmdbox/app/features/cli/cmdbox_client_file_rmdir.py +190 -190
  21. cmdbox/app/features/cli/cmdbox_client_file_upload.py +212 -212
  22. cmdbox/app/features/cli/cmdbox_client_server_info.py +166 -166
  23. cmdbox/app/features/cli/cmdbox_server_list.py +88 -88
  24. cmdbox/app/features/cli/cmdbox_server_stop.py +138 -138
  25. cmdbox/app/features/web/cmdbox_web_audit.py +81 -0
  26. cmdbox/app/features/web/cmdbox_web_audit_metrics.py +72 -0
  27. cmdbox/app/features/web/cmdbox_web_del_cmd.py +2 -0
  28. cmdbox/app/features/web/cmdbox_web_del_pipe.py +1 -0
  29. cmdbox/app/features/web/cmdbox_web_do_signin.py +12 -2
  30. cmdbox/app/features/web/cmdbox_web_do_signout.py +1 -0
  31. cmdbox/app/features/web/cmdbox_web_exec_cmd.py +31 -2
  32. cmdbox/app/features/web/cmdbox_web_exec_pipe.py +1 -0
  33. cmdbox/app/features/web/cmdbox_web_filer download.py +43 -42
  34. cmdbox/app/features/web/cmdbox_web_filer.py +1 -0
  35. cmdbox/app/features/web/cmdbox_web_filer_upload.py +65 -64
  36. cmdbox/app/features/web/cmdbox_web_gui.py +166 -165
  37. cmdbox/app/features/web/cmdbox_web_load_pin.py +43 -43
  38. cmdbox/app/features/web/cmdbox_web_raw_pipe.py +87 -87
  39. cmdbox/app/features/web/cmdbox_web_save_cmd.py +1 -0
  40. cmdbox/app/features/web/cmdbox_web_save_pin.py +42 -42
  41. cmdbox/app/features/web/cmdbox_web_save_pipe.py +1 -0
  42. cmdbox/app/features/web/cmdbox_web_user_data.py +58 -0
  43. cmdbox/app/features/web/cmdbox_web_users.py +12 -0
  44. cmdbox/app/options.py +788 -601
  45. cmdbox/app/web.py +7 -1
  46. cmdbox/extensions/features.yml +23 -0
  47. cmdbox/extensions/sample_project/sample/app/features/cli/sample_client_time.py +82 -82
  48. cmdbox/extensions/sample_project/sample/app/features/cli/sample_server_time.py +145 -145
  49. cmdbox/extensions/user_list.yml +5 -0
  50. cmdbox/licenses/{LICENSE.Sphinx.8.1.3(BSD License).txt → LICENSE.Sphinx.8.2.3(UNKNOWN).txt} +1 -1
  51. cmdbox/licenses/LICENSE.argcomplete.3.6.2(Apache Software License).txt +177 -0
  52. cmdbox/licenses/{LICENSE.babel.2.16.0(BSD License).txt → LICENSE.babel.2.17.0(BSD License).txt } +1 -1
  53. cmdbox/licenses/{LICENSE.pkginfo.1.10.0(MIT License).txt → LICENSE.charset-normalizer.3.4.1(MIT License).txt } +1 -1
  54. cmdbox/licenses/LICENSE.gevent.25.4.1(MIT).txt +25 -0
  55. cmdbox/licenses/LICENSE.greenlet.3.2.0(MIT AND Python-2.0).txt +30 -0
  56. cmdbox/licenses/LICENSE.gunicorn.23.0.0(MIT License).txt +23 -0
  57. cmdbox/licenses/LICENSE.importlib_metadata.8.6.1(Apache Software License).txt +202 -0
  58. cmdbox/licenses/LICENSE.nh3.0.2.21(MIT).txt +21 -0
  59. cmdbox/licenses/{LICENSE.pillow.11.0.0(CMU License (MIT-CMU)).txt → LICENSE.pillow.11.1.0(CMU License (MIT-CMU)).txt } +27 -40
  60. cmdbox/licenses/LICENSE.pillow.11.2.1(UNKNOWN).txt +1200 -0
  61. cmdbox/licenses/LICENSE.plyer.2.1.0(MIT License).txt +19 -0
  62. cmdbox/licenses/LICENSE.prompt_toolkit.3.0.50(BSD License).txt +27 -0
  63. cmdbox/licenses/LICENSE.prompt_toolkit.3.0.51(BSD License).txt +27 -0
  64. cmdbox/licenses/LICENSE.psycopg-binary.3.2.6(GNU Lesser General Public License v3 (LGPLv3)).txt +165 -0
  65. cmdbox/licenses/LICENSE.psycopg-pool.3.2.6(GNU Lesser General Public License v3 (LGPLv3)).txt +165 -0
  66. cmdbox/licenses/LICENSE.psycopg.3.2.6(GNU Lesser General Public License v3 (LGPLv3)).txt +165 -0
  67. cmdbox/licenses/LICENSE.pycryptodome.3.22.0(BSD License; Public Domain).txt +61 -0
  68. cmdbox/licenses/LICENSE.pydantic.2.11.3(MIT License).txt +21 -0
  69. cmdbox/licenses/LICENSE.pydantic_core.2.33.1(MIT License).txt +21 -0
  70. cmdbox/licenses/LICENSE.pystray.0.19.5(GNU Lesser General Public License v3 (LGPLv3)).txt +674 -0
  71. cmdbox/licenses/LICENSE.questionary.2.1.0(MIT License).txt +19 -0
  72. 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
  73. cmdbox/licenses/{LICENSE.six.1.16.0(MIT License).txt → LICENSE.six.1.17.0(MIT License).txt } +1 -1
  74. cmdbox/licenses/LICENSE.starlette.0.46.2(BSD License).txt +27 -0
  75. cmdbox/licenses/{LICENSE.charset-normalizer.3.4.0(MIT License).txt → LICENSE.typing-inspection.0.4.0(MIT License).txt } +2 -2
  76. cmdbox/licenses/LICENSE.typing_extensions.4.13.2(UNKNOWN).txt +279 -0
  77. cmdbox/licenses/LICENSE.tzdata.2025.2(Apache Software License).txt +15 -0
  78. cmdbox/licenses/LICENSE.urllib3.2.4.0(UNKNOWN).txt +21 -0
  79. cmdbox/licenses/LICENSE.uvicorn.0.34.1(BSD License).txt +27 -0
  80. cmdbox/licenses/LICENSE.watchfiles.1.0.5(MIT License).txt +21 -0
  81. cmdbox/licenses/files.txt +49 -38
  82. cmdbox/logconf_audit.yml +30 -0
  83. cmdbox/logconf_cmdbox.yml +30 -0
  84. cmdbox/version.py +2 -2
  85. cmdbox/web/assets/apexcharts/apexcharts.css +679 -0
  86. cmdbox/web/assets/apexcharts/apexcharts.min.js +38 -0
  87. cmdbox/web/assets/cmdbox/audit.js +340 -0
  88. cmdbox/web/assets/cmdbox/color_mode.css +520 -0
  89. cmdbox/web/assets/cmdbox/common.js +416 -24
  90. cmdbox/web/assets/cmdbox/filer_modal.js +1 -1
  91. cmdbox/web/assets/cmdbox/list_cmd.js +10 -275
  92. cmdbox/web/assets/cmdbox/list_pipe.js +3 -3
  93. cmdbox/web/assets/cmdbox/main.js +2 -2
  94. cmdbox/web/assets/cmdbox/result.js +2 -2
  95. cmdbox/web/assets/cmdbox/signin.js +2 -2
  96. cmdbox/web/assets/cmdbox/users.js +19 -20
  97. cmdbox/web/assets/cmdbox/view_raw.js +1 -1
  98. cmdbox/web/assets/cmdbox/view_result.js +11 -13
  99. cmdbox/web/assets/filer/filer.js +2 -2
  100. cmdbox/web/assets/filer/main.js +2 -2
  101. cmdbox/web/assets_license_list.txt +4 -1
  102. cmdbox/web/audit.html +268 -0
  103. cmdbox/web/filer.html +37 -12
  104. cmdbox/web/gui.html +36 -53
  105. cmdbox/web/result.html +24 -3
  106. cmdbox/web/signin.html +35 -14
  107. cmdbox/web/users.html +21 -3
  108. {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.3.dist-info}/METADATA +28 -5
  109. {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.3.dist-info}/RECORD +142 -103
  110. {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.3.dist-info}/entry_points.txt +0 -1
  111. cmdbox/licenses/LICENSE.nh3.0.2.18(MIT).txt +0 -1
  112. /cmdbox/licenses/{LICENSE.Jinja2.3.1.4(BSD License).txt → LICENSE.Jinja2.3.1.6(BSD License).txt} +0 -0
  113. /cmdbox/licenses/{LICENSE.Pygments.2.18.0(BSD License).txt → LICENSE.Pygments.2.19.1(BSD License).txt} +0 -0
  114. /cmdbox/licenses/{LICENSE.anyio.4.6.2.post1(MIT License).txt → LICENSE.anyio.4.9.0(MIT License).txt} +0 -0
  115. /cmdbox/licenses/{LICENSE.argcomplete.3.5.1(Apache Software License).txt → LICENSE.argcomplete.3.6.1(Apache Software License).txt} +0 -0
  116. /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
  117. /cmdbox/licenses/{LICENSE.click.8.1.7(BSD License).txt → LICENSE.click.8.1.8(BSD License).txt} +0 -0
  118. /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
  119. /cmdbox/licenses/{LICENSE.fastapi.0.115.5(MIT License).txt → LICENSE.fastapi.0.115.12(MIT License).txt} +0 -0
  120. /cmdbox/licenses/{LICENSE.importlib_metadata.8.5.0(Apache Software License).txt → LICENSE.id.1.5.0(Apache Software License).txt} +0 -0
  121. /cmdbox/licenses/{LICENSE.keyring.25.5.0(MIT License).txt → LICENSE.keyring.25.6.0(MIT License).txt} +0 -0
  122. /cmdbox/licenses/{LICENSE.more-itertools.10.5.0(MIT License).txt → LICENSE.more-itertools.10.6.0(MIT License).txt} +0 -0
  123. /cmdbox/licenses/{LICENSE.numpy.2.1.3(BSD License).txt → LICENSE.numpy.2.2.4(BSD License).txt} +0 -0
  124. /cmdbox/licenses/{LICENSE.prettytable.3.12.0(BSD License).txt → LICENSE.prettytable.3.16.0(UNKNOWN).txt} +0 -0
  125. /cmdbox/licenses/{LICENSE.pydantic.2.10.2(MIT License).txt → LICENSE.pydantic.2.11.1(MIT License).txt} +0 -0
  126. /cmdbox/licenses/{LICENSE.pydantic_core.2.27.1(MIT License).txt → LICENSE.pydantic_core.2.33.0(MIT License).txt} +0 -0
  127. /cmdbox/licenses/{LICENSE.python-dotenv.1.0.1(BSD License).txt → LICENSE.python-dotenv.1.1.0(BSD License).txt} +0 -0
  128. /cmdbox/licenses/{LICENSE.python-multipart.0.0.17(Apache Software License).txt → LICENSE.python-multipart.0.0.20(Apache Software License).txt} +0 -0
  129. /cmdbox/licenses/{LICENSE.redis.5.2.0(MIT License).txt → LICENSE.redis.5.2.1(MIT License).txt} +0 -0
  130. /cmdbox/licenses/{LICENSE.rich.13.9.4(MIT License).txt → LICENSE.rich.14.0.0(MIT License).txt} +0 -0
  131. /cmdbox/licenses/{LICENSE.sphinx-intl.2.3.0(BSD License).txt → LICENSE.sphinx-intl.2.3.1(BSD License).txt} +0 -0
  132. /cmdbox/licenses/{LICENSE.starlette.0.41.3(BSD License).txt → LICENSE.starlette.0.46.1(BSD License).txt} +0 -0
  133. /cmdbox/licenses/{LICENSE.tomli.2.1.0(MIT License).txt → LICENSE.tomli.2.2.1(MIT License).txt} +0 -0
  134. /cmdbox/licenses/{LICENSE.twine.5.1.1(Apache Software License).txt → LICENSE.twine.6.1.0(Apache Software License).txt} +0 -0
  135. /cmdbox/licenses/{LICENSE.typing_extensions.4.12.2(Python Software Foundation License).txt → LICENSE.typing_extensions.4.13.0(UNKNOWN).txt} +0 -0
  136. /cmdbox/licenses/{LICENSE.urllib3.2.2.3(MIT License).txt → LICENSE.urllib3.2.3.0(MIT License).txt} +0 -0
  137. /cmdbox/licenses/{LICENSE.uvicorn.0.32.1(BSD License).txt → LICENSE.uvicorn.0.34.0(BSD License).txt} +0 -0
  138. /cmdbox/licenses/{LICENSE.watchfiles.1.0.0(MIT License).txt → LICENSE.watchfiles.1.0.4(MIT License).txt} +0 -0
  139. /cmdbox/licenses/{LICENSE.websockets.14.1(BSD License).txt → LICENSE.websockets.15.0.1(BSD License).txt} +0 -0
  140. /cmdbox/licenses/{LICENSE.zope.interface.7.1.1(Zope Public License).txt → LICENSE.zope.interface.7.2(Zope Public License).txt} +0 -0
  141. {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.3.dist-info}/LICENSE +0 -0
  142. {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.3.dist-info}/WHEEL +0 -0
  143. {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
- return RedirectResponse(url=f'../{next}?warn=passchange', headers=dict(signin="success")) # nginxのリバプロ対応のための相対パス
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
- return RedirectResponse(url=f'../{next}{"?warn=passchange" if notify_passchange else ""}', headers=dict(signin="success")) # nginxのリバプロ対応のための相対パス
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
  """