diff --git a/.gitignore b/.gitignore index ff3063db..31a6004e 100644 --- a/.gitignore +++ b/.gitignore @@ -67,6 +67,6 @@ conf/conf.json logs .idea temp -web/css/libs -web/css/fonts/libs -web/js/libs \ No newline at end of file +web +web-src/node_modules +web-src/package-lock.json \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 88185aac..08d80c29 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,49 @@ -language: python -python: 3.4 +if: tag IS blank +sudo: required +dist: xenial +language: node_js +node_js: +- '6' +cache: + directories: + - web-src/node_modules +addons: + chrome: stable +env: + global: + - OWNER=${TRAVIS_REPO_SLUG%/*} + - secure: "GtWCiMPNz3MDDTvXqaVsgZBvlxYRZuTY6sUEhWTL37zJZHgRLlxTCTD27hHZp2P4B6G6KGV/0iEvMYxqzo6jMrzcEnt7TlGetteqI18dhV1dIQGH6uy8Y/PktkK2g2FuJeGV3FRK4+a21v6zzSuUaFa26k97mPapa4LS83XXj7rc13ll23HhhtObVF/a1n3U0Xwe4FkdoxbKlecJUnNujESxFk4xQl4N1tFv15fldDlFq0XWs26eEp3LU/n7wkMzbg9WEqPvDeaYlvir9VpvcYWpVvf8Uz+wpvW1jnE2R6bK1TDv5BNzQwbf2JtN284yZ8I1nwZtOJkc1RUr5wUFxCD0p8tc30sAlKemI385v2t1ccoBYMwH8LHFIUoSXoolHZBsnZeADqpo3a0d8hVFWv4AcxC27Q6SfMCltl0+ogAoQ7wVfkRT++044p415Ar4raEqIkqTm64FaRNMS0v2y5mS2634PMSGMRnK+NBrWz1yFKxsiuPKypIWygtrJ4pyiL2yPBZupnCluEgqva3q6AmMDlNuSUmcTEnCRGB01U2if6/oSEgISH1VK2lsRSxuoG5dFFuezwv90YENh/7pw0/hgge7EOse6OzDsU3uNRWcTuXF7eEBhjM1wBHWHlaAwV9vfHMZX4sHWP4R4CZhPjVEPRz6HPEg1tFCs2EvkuI=" +before_install: +- sudo apt-get -y install python3-pip python3-setuptools install: - - pip install -r requirements.txt - - pip install ldap3 +- sudo pip3 install -r requirements.txt +- sudo pip3 install ldap3 +- sudo pip3 install pyasn1 --upgrade +- cd web-src +- npm install +- cd .. before_script: - - cd src - - python3 -m unittest discover -s tests -p "*.py" - - cd .. +- cd src +- python3 -m unittest discover -s tests -p "*.py" +- cd ../web-src +- npm run test +- cd .. script: - - python3 tools/build.py \ No newline at end of file +- python3 tools/build.py +before_deploy: +- git config --local user.name 'bugy' +- git config --local user.email 'buggygm@gmail.com' +- git tag -f 'dev' +- git remote add gh https://${OWNER}:${GITHUB_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git +- git push -f gh dev +- git remote remove gh +deploy: + provider: releases + name: dev + api_key: $GITHUB_TOKEN + file: build/script-server.zip + prerelease: true + overwrite: true + skip_cleanup: true + on: + branch: master \ No newline at end of file diff --git a/README.md b/README.md index 1950a45c..f983b32c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Build Status](https://travis-ci.org/bugy/script-server.svg?branch=master)](https://travis-ci.org/bugy/script-server) + # script-server Script-server is a Web GUI and a web server for scripts. @@ -41,16 +43,19 @@ Any more or less up to date browser with enabled JS Internet connection is not needed. All the files are loaded from the server. ## Installation -### Non-developer mode -1. Download script-server.zip file from [Latest release](https://github.com/bugy/script-server/releases/latest) +### For production +1. Download script-server.zip file from [Latest release](https://github.com/bugy/script-server/releases/latest) or [Dev release](https://github.com/bugy/script-server/releases/tag/dev) 2. Create script-server folder anywhere on your PC and extract zip content to this folder -(For detailed steps on linux with virtualenv, please see [Installation guide](https://github.com/bugy/script-server/wiki/Installing-on-virtualenv-(linux)) +(For detailed steps on linux with virtualenv, please see [Installation guide](https://github.com/bugy/script-server/wiki/Installing-on-virtualenv-(linux))) -### Developer mode +### For development 1. Clone/download the repository -2. Run 'tools/init.py --dev' script (this will download javascript libraries) +2. Run 'tools/init.py --dev --no-npm' script + +`init.py` script should be run after pulling any new changes +If you are making changes to web files, use `npm run build:dev` or `npm run start:dev` ## Setup and run 1. Create configurations for your scripts in *conf/runners/* folder (see [script config page](https://github.com/bugy/script-server/wiki/Script-config) for details) diff --git a/samples/scripts/parameterized.sh b/samples/scripts/parameterized.sh index 437b9b1f..559383e5 100755 --- a/samples/scripts/parameterized.sh +++ b/samples/scripts/parameterized.sh @@ -35,3 +35,5 @@ else echo '--file_upload content:' cat "$my_file" fi + +sleep 5 \ No newline at end of file diff --git a/src/main.py b/src/main.py index e731a1d3..4d512405 100644 --- a/src/main.py +++ b/src/main.py @@ -3,6 +3,7 @@ import logging import logging.config import os +import sys import migrations.migrate from alerts.alerts_service import AlertsService @@ -17,6 +18,7 @@ from files.user_file_storage import UserFileStorage from model import server_conf from utils import tool_utils, file_utils +from utils.tool_utils import InvalidWebBuildException from web import server parser = argparse.ArgumentParser(description='Launch script-server.') @@ -49,7 +51,11 @@ def get_secret(temp_folder): def main(): - tool_utils.validate_web_imports_exist(os.getcwd()) + try: + tool_utils.validate_web_build_exists(os.getcwd()) + except InvalidWebBuildException as e: + print(str(e)) + sys.exit(-1) logging_conf_file = os.path.join(CONFIG_FOLDER, 'logging.json') with open(logging_conf_file, 'rt') as f: diff --git a/src/tests/auth_ldap_test.py b/src/tests/auth_ldap_test.py index 7d133c2a..40e1426c 100644 --- a/src/tests/auth_ldap_test.py +++ b/src/tests/auth_ldap_test.py @@ -28,7 +28,7 @@ def connect(username, password): ) for dn, attrs in self._entries.items(): - dn = safe_dn(dn) + dn = safe_dn(dn).lower() entry_added = connection.strategy.add_entry(dn, attrs) if not entry_added: diff --git a/src/tests/combobox_test.js b/src/tests/combobox_test.js deleted file mode 100644 index fe892433..00000000 --- a/src/tests/combobox_test.js +++ /dev/null @@ -1,302 +0,0 @@ -'use strict'; - -var assert = chai.assert; -chai.config.truncateThreshold = 0; - -describe('Test ComboBox', function () { - - before(function () { - - }); - beforeEach(function () { - this.comboBox = createVue('combobox', { - config: { - required: false, - name: 'List param X', - description: 'some param', - values: ['Value A', 'Value B', 'Value C'], - multiselect: false - }, - value: 'Value B' - }); - this.element = this.comboBox.$el; - - this.currentError = null; - this.comboBox.$on('error', function (value) { - this.currentError = value - }.bind(this)); - }); - - afterEach(async function () { - await vueTicks(); - this.comboBox.$destroy(); - }); - - after(function () { - }); - - describe('Test config', function () { - - it('Test initial name', function () { - assert.equal('List param X', $(this.element).find('select').get(0).id); - assert.equal('List param X', $(this.element).find('label').get(0).innerText); - }); - - it('Test change name', async function () { - this.comboBox.config.name = 'testName1'; - - await vueTicks(); - - assert.equal('testName1', $(this.element).find('select').get(0).id); - assert.equal('testName1', $(this.element).find('label').get(0).innerText); - }); - - it('Test initial required', function () { - assert.equal(false, $(this.element).find('select').get(0).required); - }); - - it('Test change required', async function () { - this.comboBox.config.required = true; - - await vueTicks(); - - assert.equal(true, $(this.element).find('select').get(0).required); - }); - - it('Test initial description', function () { - assert.equal('some param', this.element.title); - }); - - it('Test change description', async function () { - this.comboBox.config.description = 'My new desc'; - - await vueTicks(); - - assert.equal('My new desc', this.element.title); - }); - - it('Test initial multiselect', function () { - assert.notExists($(this.element).find('select').attr('multiple')); - - const listElement = $(this.element).find('ul').get(0); - assert.equal(false, hasClass(listElement, 'multiple-select-dropdown')); - }); - - - it('Test initial allowed values', function () { - const values = ['Value A', 'Value B', 'Value C']; - - const listChildren = $(this.element).find('li'); - - assert.equal(values.length, listChildren.length - 1); - - assert.equal('Choose your option', listChildren.get(0).innerText); - - for (let i = 0; i < values.length; i++) { - const value = values[i]; - assert.equal(value, listChildren.get(i + 1).innerText); - } - }); - - it('Test change allowed values', async function () { - const values = ['val1', 'val2', 'hello', 'another option']; - this.comboBox.config.values = values; - - await vueTicks(); - - const listChildren = $(this.element).find('li'); - assert.equal(values.length, listChildren.length - 1); - - assert.equal('Choose your option', listChildren.get(0).innerText); - - for (let i = 0; i < values.length; i++) { - const value = values[i]; - assert.equal(value, listChildren.get(i + 1).innerText); - } - }); - }); - - describe('Test values', function () { - - it('Test initial value', async function () { - await vueTicks(); - - assert.equal(this.comboBox.value, 'Value B'); - - const selectedOption = $(this.element).find(":selected").text(); - assert.equal(selectedOption, 'Value B'); - }); - - it('Test external value change', async function () { - this.comboBox.value = 'Value C'; - - await vueTicks(); - - assert.equal(this.comboBox.value, 'Value C'); - - const selectedOption = $(this.element).find(":selected").text(); - assert.equal(selectedOption, 'Value C'); - }); - - it('Test select another value', async function () { - const selectElement = $(this.element).find('select'); - selectElement.val('Value A'); - selectElement.trigger('change'); - - await vueTicks(); - - assert.equal(this.comboBox.value, 'Value A'); - - const selectedOption = $(this.element).find(":selected").text(); - assert.equal(selectedOption, 'Value A'); - }); - - it('Test set unknown value', async function () { - this.comboBox.value = 'Xyz'; - - await vueTicks(); - - assert.isTrue(isEmptyString(this.comboBox.value)); - assert.equal(1, $(this.element).find(":selected").size()); - }); - - it('Test set unknown value', async function () { - this.comboBox.value = 'Xyz'; - - await vueTicks(); - - assert.isTrue(isEmptyString(this.comboBox.value)); - assert.equal('Choose your option', $(this.element).find(":selected").text()); - }); - - it('Test set multiselect single value', async function () { - this.comboBox.config.multiselect = true; - await vueTicks(); - - this.comboBox.value = 'Value A'; - - await vueTicks(); - assert.equal(['Value A'], this.comboBox.value); - assert.equal('Value A', $(this.element).find(":selected").text()); - }); - - it('Test set multiselect multiple values', async function () { - this.comboBox.config.multiselect = true; - await vueTicks(); - - this.comboBox.value = ['Value A', 'Value C']; - - await vueTicks(); - assert.deepEqual(['Value A', 'Value C'], this.comboBox.value); - let selectedElements = $(this.element).find(":selected"); - assert.equal(2, selectedElements.size()); - assert.equal('Value A', selectedElements.get(0).textContent); - assert.equal('Value C', selectedElements.get(1).textContent); - }); - - it('Test set multiselect single unknown value', async function () { - this.comboBox.config.multiselect = true; - await vueTicks(); - - this.comboBox.value = ['Value X']; - - await vueTicks(); - assert.deepEqual([], this.comboBox.value); - assert.equal('Choose your option', $(this.element).find(":selected").text()); - }); - - it('Test set multiselect unknown value from multiple', async function () { - this.comboBox.config.multiselect = true; - await vueTicks(); - - this.comboBox.value = ['Value A', 'Value X']; - - await vueTicks(); - assert.deepEqual(['Value A'], this.comboBox.value); - assert.equal('Value A', $(this.element).find(":selected").text()); - }); - - it('Test select multiple values in multiselect', async function () { - this.comboBox.config.multiselect = true; - await vueTicks(); - - const values = ['Value A', 'Value C']; - const selectElement = $(this.element).find('select'); - selectElement.val(values); - selectElement.trigger('change'); - - await vueTicks(); - - assert.deepEqual(values, this.comboBox.value); - let selectedElements = $(this.element).find(":selected"); - assert.equal(2, selectedElements.size()); - assert.equal('Value A', selectedElements.get(0).textContent); - assert.equal('Value C', selectedElements.get(1).textContent); - }); - - it('Test change allowed values with matching value', async function () { - this.comboBox.config.values = ['val1', 'val2', 'hello', 'Value B', 'another option']; - - await vueTicks(); - - assert.equal('Value B', this.comboBox.value); - assert.equal('Value B', $(this.element).find(":selected").text()); - }); - - it('Test change allowed values with unmatching value', async function () { - this.comboBox.config.values = ['val1', 'val2', 'hello', 'another option']; - - await vueTicks(); - - assert.isTrue(isEmptyString(this.comboBox.value)); - assert.equal('Choose your option', $(this.element).find(":selected").text()); - }); - - it('Test change allowed values and then a value', async function () { - this.comboBox.config.values = ['val1', 'val2', 'hello', 'another option']; - this.comboBox.value = 'val2'; - - await vueTicks(); - - assert.equal('val2', this.comboBox.value); - assert.equal('val2', $(this.element).find(":selected").text()); - }); - }); - - describe('Test errors', function () { - it('Test set external empty value when required', async function () { - this.comboBox.config.required = true; - await vueTicks(); - - this.comboBox.value = ''; - - await vueTicks(); - - assert.equal('required', this.currentError); - }); - - it('Test unselect combobox when required', async function () { - this.comboBox.config.required = true; - await vueTicks(); - - const selectElement = $(this.element).find('select'); - selectElement.val(''); - selectElement.trigger('change'); - - await vueTicks(); - - assert.equal('required', this.currentError); - }); - - it('Test set external value after empty', async function () { - this.comboBox.config.required = true; - this.comboBox.value = ''; - await vueTicks(); - - this.comboBox.value = 'Value A'; - await vueTicks(); - - assert.equal('', this.currentError); - }); - }); -}); \ No newline at end of file diff --git a/src/tests/script_view_conf_test.js b/src/tests/script_view_conf_test.js deleted file mode 100644 index b7cbbe63..00000000 --- a/src/tests/script_view_conf_test.js +++ /dev/null @@ -1,42 +0,0 @@ -"use strict"; - -var assert = chai.assert; -chai.config.truncateThreshold = 0; - -describe('Test Configuration of ScriptView', function () { - - beforeEach(function () { - this.viewContainer = document.createElement('div'); - document.children[0].appendChild(this.viewContainer); - - this.scriptView = new ScriptView(this.viewContainer); - this.vueModel = this.scriptView.vueModel; - }); - - afterEach(function () { - this.viewContainer.remove(); - }); - - describe('Test descript section', function () { - - it('test simple text', function () { - this.scriptView.setScriptDescription('some text'); - assert.equal('some text', this.vueModel.formattedDescription) - }); - - it('test bold', function () { - this.scriptView.setScriptDescription('some **bold** text'); - assert.equal('some bold text', this.vueModel.formattedDescription) - }); - - it('test explicit link', function () { - this.scriptView.setScriptDescription('some [link_text](https://google.com)'); - assert.equal('some link_text', this.vueModel.formattedDescription) - }); - - it('test new line', function () { - this.scriptView.setScriptDescription('line1\nline2'); - assert.equal('line1
line2', this.vueModel.formattedDescription) - }); - }) -}); diff --git a/src/tests/test_utils.js b/src/tests/test_utils.js deleted file mode 100644 index 8acfd05c..00000000 --- a/src/tests/test_utils.js +++ /dev/null @@ -1,32 +0,0 @@ -async function vueTicks(count) { - if (isNull(count)) { - count = 3; - } else if (count === 0) { - return Promise.resolve(null); - } - - let promise = Vue.nextTick(); - for (let i = 0; i < (count - 1); i++) { - promise.then(function () { - return Vue.nextTick(); - }); - } - return promise; -} - -function createVue(componentName, properties) { - document.body.insertAdjacentHTML('afterbegin', '
'); - const topLevelElement = document.getElementById('top-level-element'); - - let componentConstructor = Vue.options.components[componentName]; - - const vm = new componentConstructor({ - propsData: properties - }).$mount(topLevelElement); - - vm.$on('input', function (value) { - vm.value = value - }); - - return vm; -} diff --git a/src/tests/textfield_test.js b/src/tests/textfield_test.js deleted file mode 100644 index 17e5d45d..00000000 --- a/src/tests/textfield_test.js +++ /dev/null @@ -1,187 +0,0 @@ -'use strict'; - -var assert = chai.assert; -chai.config.truncateThreshold = 0; - -describe('Test TextField', function () { - - before(function () { - - }); - beforeEach(function () { - this.textfield = createVue('textfield', { - config: { - required: false, - name: 'Text paparam', - description: 'this field is used for nothing', - values: ['Value A', 'Value B', 'Value C'], - multiselect: false - }, - value: 'Hello world' - }); - this.element = this.textfield.$el; - - this.currentError = null; - this.textfield.$on('error', function (value) { - this.currentError = value - }.bind(this)); - }); - - afterEach(async function () { - await vueTicks(); - this.textfield.$destroy(); - }); - - after(function () { - }); - - describe('Test config', function () { - - it('Test initial name', function () { - assert.equal('Text paparam', $(this.element).find('label').text()); - }); - - it('Test change name', async function () { - this.textfield.config.name = 'testName1'; - - await vueTicks(); - - assert.equal('testName1', $(this.element).find('label').text()); - }); - - it('Test initial required', function () { - assert.equal(false, $(this.element).find('input').get(0).required); - }); - - it('Test change required', async function () { - this.textfield.config.required = true; - - await vueTicks(); - - assert.equal(true, $(this.element).find('input').get(0).required); - }); - - it('Test initial field type', function () { - assert.equal('text', $(this.element).find('input').attr('type')); - }); - - it('Test change field type to number', async function () { - Vue.set(this.textfield.config, 'type', 'int'); - - await vueTicks(); - - assert.equal('number', $(this.element).find('input').attr('type')); - }); - - it('Test change field type to password', async function () { - Vue.set(this.textfield.config, 'secure', true); - - await vueTicks(); - - assert.equal('password', $(this.element).find('input').attr('type')); - }); - }); - - describe('Test values', function () { - - it('Test initial value', async function () { - await vueTicks(); - - assert.equal(this.textfield.value, 'Hello world'); - assert.equal('Hello world', $(this.element).find('input').val()); - }); - - it('Test external value change', async function () { - this.textfield.value = 'XYZ'; - - await vueTicks(); - - assert.equal(this.textfield.value, 'XYZ'); - assert.equal('XYZ', $(this.element).find('input').val()); - }); - - it('Test change value by user', async function () { - const inputField = $(this.element).find('input'); - setInputValue(inputField, 'abc def', true); - - await vueTicks(); - - assert.equal(this.textfield.value, 'abc def'); - assert.equal('abc def', inputField.val()); - }); - - it('Test empty value on init', async function () { - let textfield; - try { - textfield = createVue('textfield', { - config: { - name: 'Text param', - }, - value: '' - }); - assert.equal(textfield.value, ''); - } finally { - if (textfield) { - await vueTicks(); - textfield.$destroy(); - } - } - }); - }); - - describe('Test validaton', function () { - it('Test set external empty value when required', async function () { - this.textfield.config.required = true; - this.textfield.value = ''; - - await vueTicks(); - - assert.equal('required', this.currentError); - }); - - it('Test user set empty value when required', async function () { - this.textfield.config.required = true; - await vueTicks(); - - const inputField = $(this.element).find('input'); - setInputValue(inputField, '', true); - - await vueTicks(); - - assert.equal('required', this.currentError); - }); - - it('Test set external value after empty when required', async function () { - this.textfield.config.required = true; - this.textfield.value = ''; - await vueTicks(); - - this.textfield.value = 'A'; - - await vueTicks(); - - assert.equal('', this.currentError); - }); - - it('Test user set value after empty when required', async function () { - this.textfield.config.required = true; - this.textfield.value = ''; - await vueTicks(); - const inputField = $(this.element).find('input'); - - setInputValue(inputField, 'A', true); - - await vueTicks(); - - assert.equal('', this.currentError); - }); - - it('Test set invalid external value when integer', async function () { - this.textfield.config.type = 'int'; - this.textfield.value = '1.5'; - await vueTicks(); - - assert.equal('integer expected', this.currentError); - }); - }); -}); \ No newline at end of file diff --git a/src/utils/tool_utils.py b/src/utils/tool_utils.py index f2b8c27e..8e4dfb0e 100644 --- a/src/utils/tool_utils.py +++ b/src/utils/tool_utils.py @@ -1,71 +1,23 @@ import os -import re -from html.parser import HTMLParser -from utils import file_utils - - -def get_import_paths(project_path): - imports = set() - - class HtmlImportSearcher(HTMLParser): - def handle_starttag(self, tag, attrs): - if tag == 'script': - for attr in attrs: - if attr[0] == 'src': - imports.add(attr[1]) - - if tag == 'link': - for attr in attrs: - if attr[0] == 'href': - imports.add(attr[1]) +def validate_web_build_exists(project_path): web_folder = os.path.join(project_path, 'web') - for file in os.listdir(web_folder): - if not file.endswith('.html'): - continue - - file_path = os.path.join(web_folder, file) - - parser = HtmlImportSearcher() - parser.feed(file_utils.read_file(file_path)) - - css_folder = os.path.join(web_folder, 'css') - for file in os.listdir(css_folder): - if not file.endswith('.css'): - continue - file_path = os.path.join(css_folder, file) + how_to_fix_build_message = \ + 'How to fix: ' \ + '\n - PROD: please use stable releases from https://github.com/bugy/script-server/releases/latest' \ + '\n - DEV: please run tools/init.py --dev --no-npm' - fonts_paths = extract_font_urls_from_css(file_path) - for path in fonts_paths: - imports.add(os.path.join('css', path)) - - lib_paths = [] - for import_path in imports: - if '/libs/' in import_path: - lib_path = import_path.replace('/', os.path.sep) - lib_path = os.path.join(web_folder, lib_path) - - lib_paths.append(lib_path) - - return lib_paths - - -def extract_font_urls_from_css(css_file): - content = file_utils.read_file(css_file) - matches = re.findall('url\(/?((\w+/)*fonts/[^)]+)\)', content) - result = [] - for match in matches: - url = match[0] - result.append(url) - - return result + if not os.path.exists(web_folder): + raise InvalidWebBuildException(web_folder + ' does not exist. \n' + how_to_fix_build_message) + required_files = ['index.js', 'index.html', 'index-deps.css', 'admin.html', os.path.join('admin', 'admin.js')] + for file in required_files: + file_path = os.path.join(web_folder, file) + if not os.path.exists(file_path): + raise InvalidWebBuildException('web folder is invalid. \n' + how_to_fix_build_message) -def validate_web_imports_exist(project_path): - import_paths = get_import_paths(project_path) - for import_path in import_paths: - if not os.path.exists(import_path): - raise Exception(import_path + " doesn't exist, please run tools/init.py") +class InvalidWebBuildException(Exception): + pass diff --git a/src/web/server.py b/src/web/server.py index 51df1e1e..b522f5d5 100755 --- a/src/web/server.py +++ b/src/web/server.py @@ -51,15 +51,15 @@ def is_allowed_during_login(request_path, login_url, request_handler): if request_path == login_url: return True - login_resources = ['/js/login.js', - '/js/common.js', - '/js/libs/jquery.min.js', - '/js/libs/materialize.js', - '/css/libs/materialize.min.css', + login_resources = ['/login.js', + '/login.js.map', '/css/index.css', - '/css/fonts/roboto/Roboto-Regular.woff2', - '/css/fonts/roboto/Roboto-Regular.woff', - '/css/fonts/roboto/Roboto-Regular.ttf', + '/login-deps.css', + '/login-deps.css.map', + '/fonts/roboto-latin-700.woff2', + '/fonts/roboto-latin-700.woff', + '/fonts/roboto-latin-400.woff2', + '/fonts/roboto-latin-400.woff', '/images/titleBackground.jpg', '/images/g-logo-plain.png', '/images/g-logo-plain-pressed.png'] @@ -73,7 +73,7 @@ def is_allowed_during_login(request_path, login_url, request_handler): else: return False - allowed_referrers = [login_url, '/css/libs/materialize.min.css', '/css/index.css'] + allowed_referrers = [login_url, '/login-deps.css', '/css/index.css'] for allowed_referrer in allowed_referrers: if referer.endswith(allowed_referrer): return True @@ -467,7 +467,13 @@ class GetExecutingScriptValues(BaseRequestHandler): def get(self, execution_id): validate_execution_id(execution_id, self) - values = self.application.execution_service.get_parameter_values(execution_id) + values = dict(self.application.execution_service.get_parameter_values(execution_id)) + + config = self.application.execution_service.get_config(execution_id) + for parameter in config.parameters: + parameter_name = parameter.name + if (parameter_name in values) and (parameter.type == 'file_upload'): + del values[parameter_name] self.write(external_model.to_external_parameter_values(values)) diff --git a/tools/init.py b/tools/init.py index e8da4800..70d680b7 100755 --- a/tools/init.py +++ b/tools/init.py @@ -1,59 +1,41 @@ #!/usr/bin/env python3 - +import argparse import os import sys -import urllib.request sys.path.insert(1, os.path.join(sys.path[0], '..', 'src')) -from utils import tool_utils - -LIBRARIES = { - 'materialize.min.css': 'https://cdnjs.cloudflare.com/ajax/libs/materialize/0.98.2/css/materialize.min.css', - 'jquery.min.js': 'https://code.jquery.com/jquery-2.1.1.min.js', - 'materialize.js': { - 'prod': 'https://cdnjs.cloudflare.com/ajax/libs/materialize/0.98.2/js/materialize.min.js', - 'dev': 'https://cdnjs.cloudflare.com/ajax/libs/materialize/0.98.2/js/materialize.js', - }, - 'hashtable.js': 'https://github.com/timdown/jshashtable/releases/download/v3.0/jshashtable-3.0.js', - 'MaterialIcons-Regular.woff2': - 'https://github.com/google/material-design-icons/blob/master/iconfont/MaterialIcons-Regular.woff2?raw=true', +from utils import process_utils - 'vue.js': { - 'prod': 'https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.15/vue.min.js', - 'dev': 'https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.15/vue.js' - }, - 'vue-router.js': { - 'prod': 'https://cdnjs.cloudflare.com/ajax/libs/vue-router/3.0.1/vue-router.min.js', - 'dev': 'https://cdnjs.cloudflare.com/ajax/libs/vue-router/3.0.1/vue-router.js' - }, - 'marked.js': { - 'prod': 'https://cdnjs.cloudflare.com/ajax/libs/marked/0.4.0/marked.min.js', - 'dev': 'https://cdnjs.cloudflare.com/ajax/libs/marked/0.4.0/marked.js' - } -} +def download_web_files(project_path): + print('Downloading web files...') + from io import BytesIO + from zipfile import ZipFile + from urllib.request import urlopen -def prepare_project(project_path, prod=True): - import_paths = tool_utils.get_import_paths(project_path) + response = urlopen('https://github.com/bugy/script-server/releases/download/dev/script-server.zip') + with ZipFile(BytesIO(response.read())) as zipfile: + for file in zipfile.namelist(): + if file.startswith('web/'): + zipfile.extract(file, project_path) - for import_path in import_paths: - library = os.path.basename(import_path) + print('Done') - if library not in LIBRARIES: - raise Exception(library + ' library URL is not specified') - lib_parent_path = os.path.dirname(import_path) - if not os.path.exists(lib_parent_path): - os.makedirs(lib_parent_path) +def build_web_files(prod, project_path): + print('Building web...') + npm_mode = 'prod' if prod else 'dev' + process_utils.invoke('npm run build:' + npm_mode, os.path.join(project_path, 'web-src')) + print('Done') - print('Downloading library ' + library + '...') - url = LIBRARIES[library] - if isinstance(url, dict): - url = url['prod'] if prod else url['dev'] - urllib.request.urlretrieve(url, import_path) +def prepare_project(project_path, *, prod, download_web=False): + if download_web: + download_web_files(project_path) + else: + build_web_files(prod, project_path) runners_conf = os.path.join(project_path, 'conf', 'runners') if not os.path.exists(runners_conf): @@ -67,6 +49,9 @@ def prepare_project(project_path, prod=True): else: project_path = '' - prod = '--dev' not in sys.argv + parser = argparse.ArgumentParser(description='Initializes source code repo to make it runnable') + parser.add_argument('--no-npm', action='store_true', default=False) + parser.add_argument('--dev', action='store_true', default=False) + args = vars(parser.parse_args()) - prepare_project(project_path, prod) + prepare_project(project_path, prod=not args['dev'], download_web=args['no_npm']) diff --git a/web-src/admin.html b/web-src/admin.html new file mode 100644 index 00000000..33e93905 --- /dev/null +++ b/web-src/admin.html @@ -0,0 +1,23 @@ + + + + + Admin panel + + <% for (var css in htmlWebpackPlugin.files.css) { %> + + <% } %> + + + + + <% for (var chunk in htmlWebpackPlugin.files.chunks) { %> + + <% } %> + + +
+ +
+ + \ No newline at end of file diff --git a/web-src/css/admin.css b/web-src/css/admin.css new file mode 100644 index 00000000..e9e4df12 --- /dev/null +++ b/web-src/css/admin.css @@ -0,0 +1,33 @@ +body { + background: white; + font-family: "Roboto", sans-serif; + font-weight: normal; +} + +.page { + height: 100vh; +} + +.page-title { + width: 100%; + height: 64px; + line-height: 64px; + + padding-left: 32px; + + font-size: 18px; + font-weight: 400; + + vertical-align: baseline; + + color: #FFF; + + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; +} + +.page .section { + height: calc(100vh - 64px); + width: 100%; + overflow: auto; +} diff --git a/web/css/bash_styles.css b/web-src/css/bash_styles.css similarity index 100% rename from web/css/bash_styles.css rename to web-src/css/bash_styles.css diff --git a/web/css/index.css b/web-src/css/index.css similarity index 71% rename from web/css/index.css rename to web-src/css/index.css index f3f9bcc6..31c7679e 100644 --- a/web/css/index.css +++ b/web-src/css/index.css @@ -50,8 +50,9 @@ body { } #searchField { - width: calc(100% - 8px); + width: 100%; height: 1.5rem; + font-size: 1rem; float: right; padding: 0; margin: 0; @@ -59,17 +60,16 @@ body { #searchPanel { padding: 16px 10px; - width: auto; - display: flex; - flex-direction: row; + width: calc(100% - 80px - 10px); vertical-align: middle; position: absolute; top: 0; - right: 54px; + right: 80px; background: white; + transition: width 0.3s; } -#searchField.collapsed { +#searchPanel.collapsed { width: 0; } @@ -241,57 +241,22 @@ body { #content-panel .script-parameters-panel .parameter input { margin: 0; - width: 100%; -} - -#content-panel .script-parameters-panel .parameter.file-upload-field > input { - position: absolute; - left: -9999px; - opacity: 0; -} -#content-panel .script-parameters-panel .parameter.file-upload-field > .btn-flat { - position: absolute; - top: -8px; - right: -8px; -} - -#content-panel .script-parameters-panel .parameter.file-upload-field > .btn-flat > i { - color: #9e9e9e; - clip-path: inset(0 0 14px 0); -} - -#content-panel .script-parameters-panel .parameter.file-upload-field label { - cursor: pointer; -} - -#content-panel .script-parameters-panel .parameter.file-upload-field .file-upload-field-value { - position: relative; - display: block; - width: 100%; - padding-right: 24px; - - overflow: hidden; - text-overflow: ellipsis; - - /* same as text field */ - border-bottom: 1px solid #9e9e9e; - height: 1.5rem; - color: inherit; + font-size: 1rem; + height: 1.5em; } -#content-panel .script-parameters-panel .parameter.file-upload-field [type="file"]:focus + * + .file-upload-field-value { - border-bottom: 1px solid #26a69a; - box-shadow: 0 1px 0 0 #26a69a; +#content-panel .script-parameters-panel .parameter > label { + transform: none; + font-size: 1rem; } -#content-panel .script-parameters-panel .parameter.file-upload-field [type="file"]:focus + .file-upload-field-label { - color: #26a69a; +#content-panel .script-parameters-panel .parameter > label.active { + transform: translateY(-70%) scale(0.8); } -#content-panel .script-parameters-panel .parameter.file-upload-field [type="file"]:invalid + * + .file-upload-field-value { - border-bottom: 1px solid #e51c23; - box-shadow: 0 1px 0 0 #e51c23; +.script-parameters-panel .input-field input[type=checkbox] + span { + padding-left: 28px; } #content-panel .log-panel { @@ -301,93 +266,12 @@ body { margin-top: 12px; } -.log-panel { - flex: 1; - - position: relative; - min-height: 0; - - background: #f4f2f0; - - width: 100%; - - border: solid 1px rgba(51, 51, 51, 0.12); - border-radius: 2px; -} - -/*noinspection CssInvalidPropertyValue,CssOverwrittenProperties*/ -.log-content { - display: block; - overflow-y: auto; - height: 100%; - - font-size: .875em; - - padding: 1.5em; - - white-space: pre-wrap; /* CSS 3 */ - white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ - white-space: -o-pre-wrap; /* Opera 7 */ - overflow-wrap: break-word; - - -ms-word-break: break-all; - /* This is the dangerous one in WebKit, as it breaks things wherever */ - word-break: break-all; - /* Instead use this non-standard one: */ - word-break: break-word; - - /* Adds a hyphen where the word breaks, if supported (No Blink) */ - -ms-hyphens: auto; - -moz-hyphens: auto; - -webkit-hyphens: auto; - hyphens: auto; -} - -.log-panel-shadow { - position: absolute; - - width: 100%; - min-height: 100%; - top: 0; - z-index: 5; - - pointer-events: none; -} - -.shadow-top-bottom { - box-shadow: 0 7px 8px -4px #888888 inset, 0 -7px 8px -4px #888888 inset; - -webkit-box-shadow: 0 7px 8px -4px #888888 inset, 0 -7px 8px -4px #888888 inset; - -moz-box-shadow: 0 7px 8px -4px #888888 inset, 0 -7px 8px -4px #888888 inset; -} - -.shadow-top { - box-shadow: 0 7px 8px -4px #888888 inset; - -webkit-box-shadow: 0 7px 8px -4px #888888 inset; - -moz-box-shadow: 0 7px 8px -4px #888888 inset; -} - -.shadow-bottom { - box-shadow: 0 -7px 8px -4px #888888 inset; - -webkit-box-shadow: 0 -7px 8px -4px #888888 inset; - -moz-box-shadow: 0 -7px 8px -4px #888888 inset; -} - -#content-panel input { - height: 1.5em; - width: auto; -} - #content-panel .input-field label { top: 0; -} -#content-panel .input-field [type="checkbox"] + label { - left: 0; - padding-left: 28px; -} - -#content-panel .input-field label.active { - transform: translateY(-90%); + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; } #content-panel .button-execute { @@ -414,6 +298,18 @@ body { #content-panel .script-input-panel input { margin: 0; width: 100%; + height: 1.5em; + font-size: 1rem; +} + +#content-panel .script-input-panel > label { + transform: translateY(-30%); + margin-left: 2px; +} + +#content-panel .script-input-panel > label.active { + color: #26a69a; + transform: translateY(-70%) scale(0.8); } #content-panel .validation-panel { @@ -476,6 +372,36 @@ h6.header { padding: 0.5rem; } +input:not([type]), +input[type=text]:not(.browser-default), +input[type=password]:not(.browser-default), +input[type=email]:not(.browser-default), +input[type=url]:not(.browser-default), +input[type=time]:not(.browser-default), +input[type=date]:not(.browser-default), +input[type=datetime]:not(.browsertele-default), +input[type=datetime-local]:not(.browser-default), +input[type=tel]:not(.browser-default), +input[type=number]:not(.browser-default), +input[type=search]:not(.browser-default), +textarea.materialize-textarea { + color: rgba(0, 0, 0, 0.87); +} + +input[type=checkbox]:not(.browser-default) + span { + color: #9e9e9e; +} + +.btn-flat:hover { + background-color: rgba(0, 0, 0, 0.1); +} + +#content-panel .select-dropdown li.disabled, +#content-panel .select-dropdown li.disabled > span, +#content-panel .select-dropdown li.optgroup { + background-color: transparent; +} + #content-panel .script-parameters-panel input[type="text"]:invalid, #content-panel .script-parameters-panel input[type="number"]:invalid { border-bottom: 1px solid #e51c23; @@ -492,17 +418,17 @@ h6.header { left: 0.75rem; } -#content-panel .script-parameters-panel .select-wrapper span.caret { - top: 2px; -} - #content-panel .script-parameters-panel .select-wrapper + label { - top: -14px; + transform: scale(0.8); + top: -18px; } #content-panel .script-parameters-panel .dropdown-content { max-width: 50vw; min-width: 100%; + white-space: nowrap; + + margin-bottom: 0; } #content-panel .script-parameters-panel .dropdown-content > li > span { @@ -543,17 +469,24 @@ h6.header { border-image: url("../images/titleBackground.jpg") 300; } +#login-panel .login-form .input-field { + margin-bottom: 0; + width: 96%; + margin-left: 2%; +} + #login-panel .login-form input[type="text"], #login-panel .login-form input[type="password"] { height: 2em; + margin-bottom: 1em; } #login-panel .login-form .input-field label { - top: 0; + transform: translateY(4px); } #login-panel .login-form .input-field label.active { - transform: translateY(-90%); + transform: translateY(-70%) scale(0.8); } #credentials-login-button { @@ -621,18 +554,3 @@ h6.header { margin-left: 10px; vertical-align: middle; } - -.readonly-field { - color: rgba(0, 0, 0, 0.87); -} - -.readonly-field label:nth-child(1) { - display: block; - font-size: 0.8rem; -} - -.readonly-field label:nth-child(2) { - display: block; - font-size: 1rem; - color: inherit; -} \ No newline at end of file diff --git a/web/favicon.ico b/web-src/favicon.ico similarity index 100% rename from web/favicon.ico rename to web-src/favicon.ico diff --git a/web/images/clear.png b/web-src/images/clear.png similarity index 100% rename from web/images/clear.png rename to web-src/images/clear.png diff --git a/web/images/console.png b/web-src/images/console.png similarity index 100% rename from web/images/console.png rename to web-src/images/console.png diff --git a/web/images/cookie.png b/web-src/images/cookie.png similarity index 100% rename from web/images/cookie.png rename to web-src/images/cookie.png diff --git a/web/images/file_download.png b/web-src/images/file_download.png similarity index 100% rename from web/images/file_download.png rename to web-src/images/file_download.png diff --git a/web/images/g-logo-plain-pressed.png b/web-src/images/g-logo-plain-pressed.png similarity index 100% rename from web/images/g-logo-plain-pressed.png rename to web-src/images/g-logo-plain-pressed.png diff --git a/web/images/g-logo-plain.png b/web-src/images/g-logo-plain.png similarity index 100% rename from web/images/g-logo-plain.png rename to web-src/images/g-logo-plain.png diff --git a/web/images/github.png b/web-src/images/github.png similarity index 100% rename from web/images/github.png rename to web-src/images/github.png diff --git a/web/images/logout.png b/web-src/images/logout.png similarity index 100% rename from web/images/logout.png rename to web-src/images/logout.png diff --git a/web/images/search.png b/web-src/images/search.png similarity index 100% rename from web/images/search.png rename to web-src/images/search.png diff --git a/web/images/titleBackground.jpg b/web-src/images/titleBackground.jpg similarity index 100% rename from web/images/titleBackground.jpg rename to web-src/images/titleBackground.jpg diff --git a/web/index.html b/web-src/index.html similarity index 74% rename from web/index.html rename to web-src/index.html index 04ec0124..74d9a980 100644 --- a/web/index.html +++ b/web-src/index.html @@ -5,34 +5,30 @@ Script server - - - - - - - - - + <% for (var css in htmlWebpackPlugin.files.css) { %> + + <% } %> + + <% for (var chunk in htmlWebpackPlugin.files.chunks) { %> + + <% } %> - +

Script server

-
-
- -
- + +