pywebexec 1.9.20__py3-none-any.whl → 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pywebexec/pywebexec.py +71 -64
- pywebexec/static/css/form.css +6827 -0
- pywebexec/static/css/markdown.css +79 -0
- pywebexec/static/css/style.css +37 -6876
- pywebexec/static/css/swagger-ui.css +3 -0
- pywebexec/static/css/swagger-ui.css.map +1 -0
- pywebexec/static/images/swagger-ui.svg +1 -0
- pywebexec/static/js/executables.js +32 -48
- pywebexec/static/js/js-yaml/LICENSE +21 -0
- pywebexec/static/js/js-yaml/js-yaml.min.js +2 -0
- pywebexec/static/js/schemaform.js +90 -0
- pywebexec/static/js/script.js +3 -3
- pywebexec/static/js/swagger-form.js +95 -0
- pywebexec/static/js/swagger-ui/LICENSE +202 -0
- pywebexec/static/js/swagger-ui/swagger-ui-bundle.js +2 -0
- pywebexec/static/js/swagger-ui/swagger-ui-standalone-preset.js +2 -0
- pywebexec/swagger.yaml +4 -0
- pywebexec/templates/index.html +6 -2
- pywebexec/templates/swagger_ui.html +22 -0
- pywebexec/version.py +2 -2
- {pywebexec-1.9.20.dist-info → pywebexec-2.0.0.dist-info}/METADATA +1 -2
- {pywebexec-1.9.20.dist-info → pywebexec-2.0.0.dist-info}/RECORD +26 -13
- {pywebexec-1.9.20.dist-info → pywebexec-2.0.0.dist-info}/WHEEL +1 -1
- {pywebexec-1.9.20.dist-info → pywebexec-2.0.0.dist-info}/LICENSE +0 -0
- {pywebexec-1.9.20.dist-info → pywebexec-2.0.0.dist-info}/entry_points.txt +0 -0
- {pywebexec-1.9.20.dist-info → pywebexec-2.0.0.dist-info}/top_level.txt +0 -0
pywebexec/pywebexec.py
CHANGED
@@ -28,7 +28,6 @@ import logging
|
|
28
28
|
from pathlib import Path
|
29
29
|
import pyte
|
30
30
|
from . import host_ip
|
31
|
-
from flask_swagger_ui import get_swaggerui_blueprint # new import
|
32
31
|
import yaml
|
33
32
|
|
34
33
|
if os.environ.get('PYWEBEXEC_LDAP_SERVER'):
|
@@ -609,18 +608,6 @@ if args.cert:
|
|
609
608
|
app.config['SESSION_COOKIE_SECURE'] = True
|
610
609
|
app.config['TITLE'] = f"{args.title} API"
|
611
610
|
|
612
|
-
# Register Swagger UI blueprint with safe token included in API_URL
|
613
|
-
SWAGGER_URL = '/v0/documentation'
|
614
|
-
API_URL = '/swagger.yaml'
|
615
|
-
swaggerui_blueprint = get_swaggerui_blueprint(
|
616
|
-
SWAGGER_URL,
|
617
|
-
API_URL,
|
618
|
-
config={
|
619
|
-
'app_name': f"{args.title} API",
|
620
|
-
'layout': 'BaseLayout'
|
621
|
-
}
|
622
|
-
)
|
623
|
-
app.register_blueprint(swaggerui_blueprint, url_prefix=SWAGGER_URL)
|
624
611
|
|
625
612
|
def log_info(fromip, user, message):
|
626
613
|
app.logger.info(f"{user} {fromip}: {message}")
|
@@ -654,6 +641,7 @@ def get_executables():
|
|
654
641
|
executables_list.append(exe)
|
655
642
|
return sorted(executables_list, key=lambda x: x["command"])
|
656
643
|
|
644
|
+
|
657
645
|
@app.route('/commands/<command_id>/stop', methods=['PATCH'])
|
658
646
|
def stop_command(command_id):
|
659
647
|
log_request(f"stop_command {command_id}")
|
@@ -785,9 +773,69 @@ def list_executables():
|
|
785
773
|
return jsonify({"commands": executables_list})
|
786
774
|
|
787
775
|
|
776
|
+
|
788
777
|
@app.route('/commands/<cmd>', methods=['POST'])
|
789
778
|
def run_dynamic_command(cmd):
|
790
779
|
# Validate that 'cmd' is an executable in the current directory
|
780
|
+
|
781
|
+
def parse_params(cmd, data_params):
|
782
|
+
if not data_params:
|
783
|
+
return []
|
784
|
+
# Convert received parameters to exec args.
|
785
|
+
# If schema defined for each para, value in data:
|
786
|
+
# - if value is True → --<key>
|
787
|
+
# - if value is a string → --<param> value
|
788
|
+
# - if value is an array → --<param> value1 value2 ...
|
789
|
+
# - if value is False → "" (omit)
|
790
|
+
# schema_options:
|
791
|
+
# separator: " " (default) or "=" is the separator between --param and value
|
792
|
+
# noprefix_params: ["param1", "param2"] or ["*"] to omit --param prefix
|
793
|
+
# convert_params: {"param1": "param2"} to convert param1 to param2
|
794
|
+
exe = get_executable(cmd)
|
795
|
+
separator = exe.get("schema", {}).get("schema_options", {}).get("separator", " ")
|
796
|
+
noprefix = exe.get("schema", {}).get("schema_options", {}).get("noprefix_params", {})
|
797
|
+
convert_params = exe.get("schema", {}).get("schema_options", {}).get("convert_params", {})
|
798
|
+
schema_params = exe.get("schema", {}).get("properties", {})
|
799
|
+
if isinstance(data_params, dict) and not schema_params:
|
800
|
+
return None
|
801
|
+
if isinstance(data_params, dict):
|
802
|
+
params = ""
|
803
|
+
for param in schema_params.keys():
|
804
|
+
if not param in data_params:
|
805
|
+
continue
|
806
|
+
value = data_params[param]
|
807
|
+
if value is None:
|
808
|
+
continue
|
809
|
+
prefix = ""
|
810
|
+
if param in convert_params:
|
811
|
+
param = convert_params[param]
|
812
|
+
prefix = param
|
813
|
+
if param in ['--', '', None]:
|
814
|
+
separator = ' '
|
815
|
+
elif "*" in noprefix or param in noprefix:
|
816
|
+
separator = ""
|
817
|
+
else:
|
818
|
+
prefix = f"--{param}"
|
819
|
+
if isinstance(value, bool):
|
820
|
+
if value:
|
821
|
+
params += f"{prefix} "
|
822
|
+
continue
|
823
|
+
params += f"{prefix}{separator}"
|
824
|
+
if isinstance(value, list):
|
825
|
+
params += " ".join(value)
|
826
|
+
else:
|
827
|
+
params += str(value)
|
828
|
+
params += " "
|
829
|
+
else:
|
830
|
+
params = data_params
|
831
|
+
if isinstance(params, str):
|
832
|
+
params = [params]
|
833
|
+
try:
|
834
|
+
params = shlex.split(' '.join(params)) if isinstance(params, list) else []
|
835
|
+
except Exception as e:
|
836
|
+
return None
|
837
|
+
return params
|
838
|
+
|
791
839
|
cmd_path = os.path.join(".", os.path.basename(cmd))
|
792
840
|
if not os.path.isfile(cmd_path) or not os.access(cmd_path, os.X_OK):
|
793
841
|
return jsonify({'error': 'Command not found or not executable'}), 400
|
@@ -795,54 +843,9 @@ def run_dynamic_command(cmd):
|
|
795
843
|
data = request.json
|
796
844
|
except Exception as e:
|
797
845
|
data = {}
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
# - if value is True → --<key>
|
802
|
-
# - if value is a string → --<param> value
|
803
|
-
# - if value is an array → --<param> value1 value2 ...
|
804
|
-
# - if value is False → "" (omit)
|
805
|
-
# schema_options:
|
806
|
-
# separator: " " (default) or "=" is the separator between --param and value
|
807
|
-
# noprefix_params: ["param1", "param2"] or ["*"] to omit --param prefix
|
808
|
-
# convert_params: {"param1": "param2"} to convert param1 to param2
|
809
|
-
exe = get_executable(cmd)
|
810
|
-
separator = exe.get("schema", {}).get("schema_options", {}).get("separator", " ")
|
811
|
-
noprefix = exe.get("schema", {}).get("schema_options", {}).get("noprefix_params", {})
|
812
|
-
convert_params = exe.get("schema", {}).get("schema_options", {}).get("convert_params", {})
|
813
|
-
if data.get('params') and isinstance(data['params'], dict):
|
814
|
-
params = ""
|
815
|
-
for param, value in data['params'].items():
|
816
|
-
if value is None:
|
817
|
-
continue
|
818
|
-
prefix = ""
|
819
|
-
if param in convert_params:
|
820
|
-
param = convert_params[param]
|
821
|
-
prefix = param
|
822
|
-
if param in ['--', '', None]:
|
823
|
-
separator = ' '
|
824
|
-
elif "*" in noprefix or param in noprefix:
|
825
|
-
separator = ""
|
826
|
-
else:
|
827
|
-
prefix = f"--{param}"
|
828
|
-
if isinstance(value, bool):
|
829
|
-
if value:
|
830
|
-
params += f"{prefix} "
|
831
|
-
continue
|
832
|
-
params += f"{prefix}{separator}"
|
833
|
-
if isinstance(value, list):
|
834
|
-
params += " ".join(value)
|
835
|
-
else:
|
836
|
-
params += str(value)
|
837
|
-
params += " "
|
838
|
-
else:
|
839
|
-
params = data.get("params", [])
|
840
|
-
if isinstance(params, str):
|
841
|
-
params = [params]
|
842
|
-
try:
|
843
|
-
params = shlex.split(' '.join(params)) if isinstance(params, list) else []
|
844
|
-
except Exception as e:
|
845
|
-
return jsonify({'error': str(e)}), 400
|
846
|
+
params = parse_params(cmd, data.get('params'))
|
847
|
+
if params is None:
|
848
|
+
return jsonify({'error': 'Invalid parameters'}), 400
|
846
849
|
rows = data.get('rows', tty_rows) or tty_rows
|
847
850
|
cols = data.get('cols', tty_cols) or tty_cols
|
848
851
|
user = session.get('username', '-')
|
@@ -870,6 +873,10 @@ def get_command_status(command_id):
|
|
870
873
|
def index():
|
871
874
|
return render_template('index.html', title=args.title)
|
872
875
|
|
876
|
+
@app.route('/v0/documentation/')
|
877
|
+
def swagger_ui():
|
878
|
+
return render_template('swagger_ui.html', title=app.config.get('TITLE', 'PyWebExec API'))
|
879
|
+
|
873
880
|
@app.route('/commands', methods=['GET'])
|
874
881
|
def list_commands():
|
875
882
|
commands = read_commands()
|
@@ -982,8 +989,8 @@ def swagger_yaml():
|
|
982
989
|
"type": "object",
|
983
990
|
"properties": {
|
984
991
|
"params": {"type": "array", "items": {"type": "string"}, "default": []},
|
985
|
-
"rows": {"type": "integer", "default": tty_rows},
|
986
|
-
"cols": {"type": "integer", "default": tty_cols}
|
992
|
+
"rows": {"type": "integer", "description": "tty nb rows", "default": tty_rows},
|
993
|
+
"cols": {"type": "integer", "description": "tty nb columns", "default": tty_cols}
|
987
994
|
}
|
988
995
|
}
|
989
996
|
if exe["schema"]:
|
@@ -1019,7 +1026,7 @@ def swagger_yaml():
|
|
1019
1026
|
}
|
1020
1027
|
}
|
1021
1028
|
swagger_spec['info']['title'] = app.config.get('TITLE', 'PyWebExec API')
|
1022
|
-
swagger_spec_str = yaml.dump(swagger_spec)
|
1029
|
+
swagger_spec_str = yaml.dump(swagger_spec, sort_keys=False)
|
1023
1030
|
return Response(swagger_spec_str, mimetype='application/yaml')
|
1024
1031
|
except Exception as e:
|
1025
1032
|
return Response(f"Error reading swagger spec: {e}", status=500)
|