├── tests ├── __init__.py ├── fixtures │ ├── __init__.py │ ├── script │ │ ├── __init__.py │ │ ├── app.py │ │ └── a_script.py │ ├── click_fixtures.py │ └── flask_fixtures.py ├── conftest.py ├── test_request_parsing.py ├── test_flask_get.py ├── test_flask_post.py └── test_click_web.py ├── click_web ├── resources │ ├── __init__.py │ ├── index.py │ ├── cmd_form.py │ ├── input_fields.py │ └── cmd_exec.py ├── exceptions.py ├── static │ ├── panes.js │ ├── copy_to_clipboard.js │ ├── panes.css │ ├── open_form.js │ ├── click_web.css │ ├── post_and_read.js │ ├── split.js │ └── pure.css ├── web_click_types.py ├── templates │ ├── show_tree.html.j2 │ ├── command_form.html.j2 │ └── form_macros.html.j2 └── __init__.py ├── example ├── custom │ ├── templates │ │ └── head.html.j2 │ ├── app.py │ └── static │ │ └── custom.css ├── basic │ └── app.py ├── digest_auth │ └── app.py └── example_command.py ├── MANIFEST.in ├── doc ├── click-web-demo.gif └── click-web-example.png ├── AUTHORS.rst ├── setup.cfg ├── check.sh ├── LICENSE ├── .gitignore ├── CONTRIBUTING.rst ├── setup.py ├── .github └── workflows │ └── python-publish.yml └── README.rst /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /click_web/resources/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/script/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/custom/templates/head.html.j2: -------------------------------------------------------------------------------- 1 |
Select a command in left pane.
49 |'] 103 | continue 104 | help_.append(escape(line)) 105 | except StopIteration: 106 | break 107 | 108 | html_help += '\n'.join(help_) if in_pre else '
\n'.join(help_) 109 | return html_help 110 | -------------------------------------------------------------------------------- /click_web/static/post_and_read.js: -------------------------------------------------------------------------------- 1 | let REQUEST_RUNNING = false; 2 | 3 | function postAndRead(commandUrl) { 4 | if (REQUEST_RUNNING) { 5 | return false; 6 | } 7 | 8 | input_form = document.getElementById("inputform"); 9 | let submit_btn = document.getElementById("submit_btn"); 10 | 11 | try { 12 | REQUEST_RUNNING = true; 13 | submit_btn.disabled = true; 14 | let runner = new ExecuteAndProcessOutput(input_form, commandUrl); 15 | runner.run(); 16 | } catch (e) { 17 | console.error(e); 18 | 19 | } finally { 20 | // if we executed anything never post form 21 | // as we do not know if form already was submitted. 22 | return false; 23 | 24 | } 25 | } 26 | 27 | class ExecuteAndProcessOutput { 28 | constructor(form, commandPath) { 29 | this.form = form; 30 | this.commandUrl = commandPath; 31 | this.decoder = new TextDecoder('utf-8'); 32 | this.output_header_div = document.getElementById("output-header") 33 | this.output_wrapper_div = document.getElementById("output-wrapper") 34 | this.output_div = document.getElementById("output") 35 | this.output_footer_div = document.getElementById("output-footer") 36 | // clear old content 37 | this.output_header_div.innerHTML = ''; 38 | this.output_div.innerHTML = ''; 39 | this.output_footer_div.innerHTML = ''; 40 | // show script output 41 | this.output_header_div.hidden = false; 42 | this.output_wrapper_div.hidden = false; 43 | this.output_footer_div.hidden = false; 44 | } 45 | 46 | run() { 47 | let submit_btn = document.getElementById("submit_btn"); 48 | this.post(this.commandUrl) 49 | .then(response => { 50 | this.form.disabled = true; 51 | let reader = response.body.getReader(); 52 | return this.processStreamReader(reader); 53 | }) 54 | .then(_ => { 55 | REQUEST_RUNNING = false 56 | submit_btn.disabled = false; 57 | }) 58 | .catch(error => { 59 | console.error(error); 60 | REQUEST_RUNNING = false; 61 | submit_btn.disabled = false; 62 | 63 | } 64 | ); 65 | } 66 | 67 | post() { 68 | console.log("Posting to " + this.commandUrl); 69 | return fetch(this.commandUrl, { 70 | method: "POST", 71 | body: new FormData(this.form), 72 | // for fetch streaming only accept plain text, we wont handle html 73 | headers: {Accept: 'text/plain'} 74 | }); 75 | } 76 | 77 | async processStreamReader(reader) { 78 | while (true) { 79 | const result = await reader.read(); 80 | let chunk = this.decoder.decode(result.value); 81 | console.log(chunk); 82 | let insert_func = this.output_div.insertAdjacentText; 83 | let elem = this.output_div; 84 | 85 | // Split the read chunk into sections if needed. 86 | // Below implementation is not perfect as it expects the CLICK_WEB section markers to be 87 | // complete and not in separate chunks. However it seems to work fine 88 | // as long as the generating server yields the CLICK_WEB section in one string as they should be 89 | // quite small. 90 | if (chunk.includes(')/); 94 | for (let part of parts) { 95 | [elem, insert_func] = this.getInsertFunc(part, elem, insert_func); 96 | if (part.startsWith('' 140 | yield 'Executing: {}'.format('/'.join(str(c) for c in commands)) 141 | yield '' 142 | 143 | # important yield this block as one string so it pushed to client in one go. 144 | # so the whole block can be treated as html. 145 | html_str = '\n'.join(generate()) 146 | return html_str 147 | 148 | def _create_result_footer(self): 149 | """ 150 | Generate a footer. 151 | Note: 152 | here we always allow to generate HTML as long as we have it between CLICK-WEB comments. 153 | This way the JS frontend can insert it in the correct place in the DOM. 154 | """ 155 | # important yield this block as one string so it pushed to client in one go. 156 | # This is so the whole block can be treated as html if JS frontend. 157 | to_download = self._command_line.get_download_field_infos() 158 | lines = [''] 159 | if to_download: 160 | lines.append('Result files:
') 161 | for fi in to_download: 162 | lines.append('