diff --git a/.bowerrc b/.bowerrc index 24dc4049b..7638f18b3 100644 --- a/.bowerrc +++ b/.bowerrc @@ -1,4 +1,8 @@ { - "registry": "https://registry.bower.io", - "directory": "res/bower_components" + "registry": "http://registry.bower.io", + "directory": "/tmp/build/bower_modules", + "allow_root": true, + "off_proxy": "http://127.0.0.1:8080", + "off_https-proxy": "http://127.0.0.1:8080", + "strict-ssl": false } diff --git a/.dockerignore b/.dockerignore index 868d0d8a2..59c5a4c09 100644 --- a/.dockerignore +++ b/.dockerignore @@ -12,3 +12,4 @@ rethinkdb_data/ temp/ tmp/ .eslintcache +Dockerfile diff --git a/Dockerfile b/Dockerfile index 7bc80eea3..15fc86681 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,74 +1,117 @@ -FROM ubuntu:16.04 +FROM ubuntu:18.04 AS nodebase -# Sneak the stf executable into $PATH. -ENV PATH /app/bin:$PATH +# Install base packages +RUN export DEBIAN_FRONTEND=noninteractive && \ + apt-get update && \ + apt-get -y --no-install-recommends install curl wget libxml-bare-perl libzmq3-dev libprotobuf-dev graphicsmagick ca-certificates openjdk-8-jdk -# Work in app dir by default. -WORKDIR /app +# Install node +RUN export DEBIAN_FRONTEND=noninteractive && \ + curl -sL -o /tmp/install_node.sh https://deb.nodesource.com/setup_8.x && \ + /bin/bash /tmp/install_node.sh && \ + apt install --no-install-recommends -y nodejs -# Export default app port, not enough for all processes but it should do -# for now. -EXPOSE 3000 +RUN useradd --system --create-home --shell /usr/sbin/nologin stf + +FROM nodebase as with_packages -# Install app requirements. Trying to optimize push speed for dependant apps -# by reducing layers as much as possible. Note that one of the final steps -# installs development files for node-gyp so that npm install won't have to -# wait for them on the first native module installation. +# Install additional packages for building things RUN export DEBIAN_FRONTEND=noninteractive && \ - useradd --system \ - --create-home \ - --shell /usr/sbin/nologin \ - stf-build && \ - useradd --system \ - --create-home \ - --shell /usr/sbin/nologin \ - stf && \ - sed -i'' 's@http://archive.ubuntu.com/ubuntu/@mirror://mirrors.ubuntu.com/mirrors.txt@' /etc/apt/sources.list && \ - apt-get update && \ - apt-get -y install wget python build-essential && \ - cd /tmp && \ - wget --progress=dot:mega \ - https://nodejs.org/dist/v8.9.3/node-v8.9.3-linux-x64.tar.xz && \ - tar -xJf node-v*.tar.xz --strip-components 1 -C /usr/local && \ - rm node-v*.tar.xz && \ - su stf-build -s /bin/bash -c '/usr/local/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js install' && \ - apt-get -y install libzmq3-dev libprotobuf-dev git graphicsmagick openjdk-8-jdk yasm && \ - apt-get clean && \ - rm -rf /var/cache/apt/* /var/lib/apt/lists/* && \ - mkdir /tmp/bundletool && \ - cd /tmp/bundletool && \ - wget --progress=dot:mega \ - https://github.com/google/bundletool/releases/download/1.2.0/bundletool-all-1.2.0.jar && \ - mv bundletool-all-1.2.0.jar bundletool.jar - -# Copy app source. -COPY . /tmp/build/ - -# Give permissions to our build user. -RUN mkdir -p /app && \ - chown -R stf-build:stf-build /tmp/build /tmp/bundletool /app - -# Switch over to the build user. -USER stf-build - -# Run the build. + apt-get -y --no-install-recommends install build-essential git yasm jq python vim + +# Install node-gyp ahead of time to avoid installation on native module install +# RUN /bin/bash -c '/usr/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js install' + +# Install just the package dependencies before copying in the full source +RUN mkdir -p /tmp/build/res/build +COPY ./package*.json /tmp/build/ +WORKDIR /tmp/build RUN set -x && \ - cd /tmp/build && \ export PATH=$PWD/node_modules/.bin:$PATH && \ npm install --loglevel http && \ - npm pack && \ - tar xzf devicefarmer-stf-*.tgz --strip-components 1 -C /app && \ - bower cache clean && \ - npm prune --production && \ - mv node_modules /app && \ - rm -rf ~/.node-gyp && \ - mkdir /app/bundletool && \ - mv /tmp/bundletool/* /app/bundletool && \ - cd /app && \ - find /tmp -mindepth 1 ! -regex '^/tmp/hsperfdata_root\(/.*\)?' -delete - -# Switch to the app user. -USER stf + curl -sf https://gobinaries.com/tj/node-prune | sh + +wget --progress=dot:mega \ + https://github.com/google/bundletool/releases/download/1.2.0/bundletool-all-1.2.0.jar \ + -O /tmp/bundletool.jar + +# ********* FRONTEND ********** + +FROM with_packages as frontend + +# Install bower dependencies +WORKDIR /tmp/build +COPY ./bower.json /tmp/build/ +COPY ./.bowerrc /tmp/build/ +RUN mkdir bower_modules && \ + ./node_modules/.bin/bower install + +# Copy the app ( res ) in +COPY ./bower.json /tmp/build/ +COPY ./gulpfile.js /tmp/build/ +COPY ./webpack.config.js /tmp/build/ +COPY ./res /tmp/build/res +COPY ./lib/util /tmp/build/lib/util + +RUN ./node_modules/.bin/gulp build + +# ********* BACKEND ********** + +FROM with_packages as backend + +COPY ./lib /tmp/build/lib + +# Package and cleanup +WORKDIR /tmp/build +RUN npm pack 2>&1 | grep -v "npm notice [1-9]" && \ + mv devicefarmer-stf-$(jq .version package.json -j).tgz stf.tgz +#npm prune --production && \ +# node-prune && \ + +FROM alpine as app + +RUN mkdir -p /app +COPY --from=backend /tmp/build/stf.tgz /tmp/stf.tgz +RUN tar xf /tmp/stf.tgz --strip-components 1 -C /app + +# ********* RUNTIME ********** + +FROM nodebase as runtime + +EXPOSE 3000 + +# Setup user +RUN mkdir -p /app/res && mkdir -p /app/bundletool && chown stf:stf /app && chown stf:stf /app/* + +WORKDIR /app + +# Copy in node_modules and prune them +COPY --from=with_packages --chown=stf:stf /tmp/build/node_modules /app/node_modules +COPY --from=with_packages --chown=stf:stf /tmp/build/package.json /app/package.json +RUN npm prune --production + +# Copy in resources needed by backend +COPY --chown=stf:stf ./res/common /app/res/common +COPY --chown=stf:stf ./res/app/views /app/res/app/views +COPY --chown=stf:stf ./res/auth/mock/views /app/res/auth/mock/views + +# Copy in the backend +COPY --from=app --chown=stf:stf /app /app + +# Copy in the frontend +COPY --from=frontend --chown=stf:stf /tmp/build/res/build /app/res/build + +# Copy in bundletool +COPY --from=with_packages --chown=stf:stf /tmp/bundletool.jar /app/bundletool/bundletool.jar + +COPY ./webpackserver.config.js /app/ + +#USER root +#RUN apt-get -y --no-install-recommends install ncdu + +# Add stf executable dir into $PATH +ENV PATH /app/bin:$PATH + # Show help by default. CMD stf --help diff --git a/bower.json b/bower.json index 9fea32c57..be308c999 100644 --- a/bower.json +++ b/bower.json @@ -2,48 +2,49 @@ "name": "stf", "version": "0.1.0", "dependencies": { - "angular": "~1.5.0-rc.2", - "angular-cookies": "~1.5.0-rc.2", - "angular-route": "~1.5.0-rc.2", - "angular-sanitize": "~1.5.0-rc.2", - "angular-animate": "~1.5.0-rc.2", - "angular-touch": "~1.5.0-rc.2", "lodash": "~3.10.1", - "oboe": "~2.1.2", - "ng-table": "~1.0.0-beta.9", - "angular-gettext": "~2.2.0", - "angular-ui-ace": "~0.2.3", - "angular-dialog-service": "~5.2.11", + "oboe": "2.1.2", + "ng-table": "~1.0.0", "ng-file-upload": "~2.0.5", - "angular-growl-v2": "JanStevens/angular-growl-2#~0.7.9", "underscore.string": "~3.2.3", "bootstrap": "~3.3.6", - "font-lato-2-subset": "~0.4.0", "packery": "~1.4.3", - "draggabilly": "~1.2.4", - "angular-elastic": "~2.5.1", - "angular-hotkeys": "chieffancypants/angular-hotkeys#~1.6.0", - "angular-borderlayout": "git://github.com/filearts/angular-borderlayout.git#7c9716aebd9260763f798561ca49d6fbfd4a5c67", - "angular-ui-bootstrap": "~1.1.1", "ng-context-menu": "AdiDahan/ng-context-menu#~1.0.5", "components-font-awesome": "~4.5.0", - "epoch": "~0.8.4", - "ng-epoch": "~1.0.7", "eventEmitter": "~4.3.0", - "angular-ladda": "~0.3.1", - "d3": "~3.5.14", - "spin.js": "~2.3.2", - "angular-xeditable": "~0.1.9" + + "angular": "~1.8.0", + "angular-cookies": "~1.8.0", + "angular-route": "~1.8.0", + "angular-sanitize": "~1.8.0", + "angular-animate": "~1.8.0", + "angular-touch": "~1.8.0", + "angular-ladda": "~0.4.3", + "spin.js": "~4.1.0", + "angular-xeditable": "~0.1.9", + "angular-elastic": "~2.5.1", + "angular-ui-bootstrap": "~1.1.1", + "angular-gettext": "~2.4.1", + "angular-ui-ace": "~0.2.3", + "angular-dialog-service": "~5.2.11", + "angular-growl-v2": "JanStevens/angular-growl-2#~0.7.9", + "angular-borderlayout": "https://github.com/nanoscopic/angular-borderlayout.git#0.9.2", + "angular-hotkeys": "https://github.com/nanoscopic/angular-hotkeys.git#2.0.29", + "mousetrap": "https://github.com/nanoscopic/mousetrap.git#1.7.19", + + "epoch": "~0.8.4", + "ng-epoch": "~2.0.1" }, "private": true, "devDependencies": { "angular-mocks": "~1.5.0-rc.2" }, "resolutions": { - "angular": "~1.5.0-rc.2", "d3": "~3.5.5", - "spin.js": "~2.3.2", "eventEmitter": "~4.3.0", - "epoch": "~0.8.4" + "epoch": "~0.8.4", + "get-size": "~2.0.3", + "angular": "~1.8.0", + "oboe": "2.1.2" } } diff --git a/gulpfile.js b/gulpfile.js index efc1f9187..895967c4e 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -6,7 +6,7 @@ var jsonlint = require('gulp-jsonlint') var eslint = require('gulp-eslint') var EslintCLIEngine = require('eslint').CLIEngine var webpack = require('webpack') -var webpackConfig = require('./webpack.config').webpack +var webpackConfig = require('./webpack.config') var webpackStatusConfig = require('./res/common/status/webpack.config') var gettext = require('gulp-angular-gettext') var pug = require('gulp-pug') @@ -35,7 +35,7 @@ gulp.task('eslint', function() { return gulp.src([ 'lib/**/*.js' , 'res/**/*.js' - , '!res/bower_components/**' + , '!cache/bower/**' , '*.js' ]) // eslint() attaches the lint output to the "eslint" property @@ -75,16 +75,55 @@ gulp.task('eslint-cli', function(done) { } }) +gulp.task('clean', function(done) { + gutil.log("clean") + del.sync([ + './tmp' + //, './res/build' + , '.eslintcache' + ]) + done() +} ) -gulp.task('lint', ['jsonlint', 'eslint-cli']) -gulp.task('test', ['lint', 'run:checkversion']) -gulp.task('build', ['clean', 'webpack:build']) - -gulp.task('run:checkversion', function() { +gulp.task('check_stf_version', function() { gutil.log('Checking STF version...') return run('./bin/stf -V').exec() }) +gulp.task('webpack:build', function(callback) { + gutil.log('webpack:build') + var myConfig = webpackConfig + myConfig.plugins = myConfig.plugins.concat( + new webpack.DefinePlugin({ + 'process.env': { + NODE_ENV: JSON.stringify('production') + } + }) + ) + myConfig.devtool = false + + webpack(myConfig, function(err, stats) { + if (err) { + throw new gutil.PluginError('webpack:build', err) + } + + gutil.log('[webpack:build]', stats.toString({ + colors: true + })) + + // Save stats to a json file + // Can be analyzed in http://webpack.github.io/analyse/ + fromString('stats.json', JSON.stringify(stats.toJson())) + .pipe(gulp.dest('./tmp/')) + + callback() + }) +} ) + +gulp.task('lint', gulp.series( 'jsonlint', 'eslint-cli' ) ) +gulp.task('test', gulp.series( 'lint', 'check_stf_version' ) ) +gulp.task('build', gulp.series( 'clean', 'webpack:build' ) ) + gulp.task('karma_ci', function(done) { karma.start({ configFile: path.join(__dirname, karmaConfig) @@ -113,7 +152,7 @@ gulp.task('protractor-explorer', function(callback) { }, callback) }) -gulp.task('protractor', ['webdriver-update'], function(callback) { +gulp.task('protractor', gulp.series( 'webdriver-update', function(callback) { gulp.src(['./res/test/e2e/**/*.js']) .pipe(protractor.protractor({ configFile: protractorConfig @@ -126,7 +165,7 @@ gulp.task('protractor', ['webdriver-update'], function(callback) { /* eslint no-console: 0 */ }) .on('end', callback) -}) +} ) ) // For piping strings function fromString(filename, string) { @@ -143,37 +182,6 @@ function fromString(filename, string) { return src } - -// For production -gulp.task('webpack:build', function(callback) { - var myConfig = Object.create(webpackConfig) - myConfig.plugins = myConfig.plugins.concat( - new webpack.DefinePlugin({ - 'process.env': { - NODE_ENV: JSON.stringify('production') - } - }) - ) - myConfig.devtool = false - - webpack(myConfig, function(err, stats) { - if (err) { - throw new gutil.PluginError('webpack:build', err) - } - - gutil.log('[webpack:build]', stats.toString({ - colors: true - })) - - // Save stats to a json file - // Can be analyzed in http://webpack.github.io/analyse/ - fromString('stats.json', JSON.stringify(stats.toJson())) - .pipe(gulp.dest('./tmp/')) - - callback() - }) -}) - gulp.task('webpack:others', function(callback) { var myConfig = Object.create(webpackStatusConfig) myConfig.plugins = myConfig.plugins.concat( @@ -197,17 +205,10 @@ gulp.task('webpack:others', function(callback) { }) }) -gulp.task('translate', [ - 'translate:extract' -, 'translate:push' -, 'translate:pull' -, 'translate:compile' -]) - gulp.task('pug', function() { return gulp.src([ './res/**/*.pug' - , '!./res/bower_components/**' + , '!./bower_modules/**' ]) .pipe(pug({ locals: { @@ -221,16 +222,17 @@ gulp.task('pug', function() { .pipe(gulp.dest('./tmp/html/')) }) -gulp.task('translate:extract', ['pug'], function() { +gulp.task('translate:extract', gulp.series( 'pug', function(done) { return gulp.src([ './tmp/html/**/*.html' , './res/**/*.js' - , '!./res/bower_components/**' + , '!./bower_modules/**' , '!./res/build/**' ]) .pipe(gettext.extract('stf.pot')) .pipe(gulp.dest('./res/common/lang/po/')) -}) + done() +})) gulp.task('translate:compile', function() { return gulp.src('./res/common/lang/po/**/*.po') @@ -250,10 +252,9 @@ gulp.task('translate:pull', function() { return run('tx pull').exec() }) -gulp.task('clean', function(cb) { - del([ - './tmp' - , './res/build' - , '.eslintcache' - ], cb) -}) +gulp.task('translate', gulp.series( + 'translate:extract' +, 'translate:push' +, 'translate:pull' +, 'translate:compile' +)) \ No newline at end of file diff --git a/lib/cli/storage-temp/index.js b/lib/cli/storage-temp/index.js index 12afcb09f..33139c536 100644 --- a/lib/cli/storage-temp/index.js +++ b/lib/cli/storage-temp/index.js @@ -79,19 +79,19 @@ module.exports.builder = function(yargs) { module.exports.handler = function(argv) { return require('../../units/storage/temp')({ - port: argv.port - , saveDir: argv.saveDir - , maxFileSize: argv.maxFileSize - , bundletoolPath: argv.bundletoolPath - , keystore: { - ksPath: `/tmp/${argv.ks}.keystore` - , ksKeyAlias: argv.ksKeyAlias - , ksPass: argv.ksPass - , ksKeyPass: argv.ksKeyPass - , ksKeyalg: argv.ksKeyalg - , ksValidity: argv.ksValidity - , ksKeysize: argv.ksKeysize - , ksDname: argv.ksDname + port: argv.port, + saveDir: argv.saveDir, + maxFileSize: argv.maxFileSize, + bundletoolPath: argv.bundletoolPath, + keystore: { + ksPath: `/tmp/${argv.ks}.keystore`, + ksKeyAlias: argv.ksKeyAlias, + ksPass: argv.ksPass, + ksKeyPass: argv.ksKeyPass, + ksKeyalg: argv.ksKeyalg, + ksValidity: argv.ksValidity, + ksKeysize: argv.ksKeysize, + ksDname: argv.ksDname } }) } diff --git a/lib/units/app/index.js b/lib/units/app/index.js index ebd92063f..a52dad95a 100644 --- a/lib/units/app/index.js +++ b/lib/units/app/index.js @@ -3,6 +3,7 @@ var url = require('url') var fs = require('fs') var express = require('express') +var path = require('path') var validator = require('express-validator') var cookieSession = require('cookie-session') var bodyParser = require('body-parser') @@ -23,9 +24,32 @@ var markdownServe = require('markdown-serve') module.exports = function(options) { var log = logger.createLogger('app') + + express.static.mime.define({'application/javascript': ['js']}); + var app = express() + var server = http.createServer(app) - + + /*app.use(function (req, res, next) { + var filename = path.basename(req.url); + var extension = path.extname(filename); + if (extension === '.css') + console.log("Request: " + filename); + next(); + });*/ + app.use(express.static('/static', { + index: false, + setHeaders: (response, file_path, file_stats) => { + // This function is called when “serve-static” makes a response. + // Note that `file_path` is an absolute path. + + // Logging work + //const relative_path = path.join(asset_dir_path, path.relative(asset_dir_path, file_path)); + console.info(`@${Date.now()}`, "GAVE\t\t", file_path); + } + })); + app.use('/static/wiki', markdownServe.middleware({ rootDirectory: pathutil.root('node_modules/@devicefarmer/stf-wiki') , view: 'docs' @@ -50,7 +74,7 @@ module.exports = function(options) { log.info('Using webpack') // Keep webpack-related requires here, as our prebuilt package won't // have them at all. - var webpackServerConfig = require('./../../../webpack.config').webpackServer + var webpackServerConfig = require('./../../../webpackserver.config').webpackServer app.use('/static/app/build', require('./middleware/webpack')(webpackServerConfig)) } diff --git a/lib/units/app/middleware/webpack.js b/lib/units/app/middleware/webpack.js index e8d4a6824..6a22f9b73 100644 --- a/lib/units/app/middleware/webpack.js +++ b/lib/units/app/middleware/webpack.js @@ -9,7 +9,7 @@ var MemoryFileSystem = require('memory-fs') var logger = require('../../../util/logger') var lifecycle = require('../../../util/lifecycle') -var globalOptions = require('../../../../webpack.config').webpack +var globalOptions = require('../../../../webpack.config') // Similar to webpack-dev-middleware, but integrates with our custom // lifecycle, behaves more like normal express middleware, and removes diff --git a/lib/units/storage/plugins/apk/index.js b/lib/units/storage/plugins/apk/index.js index 9ed22bca0..daf8193e2 100644 --- a/lib/units/storage/plugins/apk/index.js +++ b/lib/units/storage/plugins/apk/index.js @@ -3,7 +3,7 @@ var url = require('url') var util = require('util') var express = require('express') -var request = require('request') +var request = require('request').defaults({ rejectUnauthorized: false }) var logger = require('../../../../util/logger') var download = require('../../../../util/download') diff --git a/lib/units/storage/plugins/apk/task/manifest.js b/lib/units/storage/plugins/apk/task/manifest.js index c6d3faf4d..3cfa3a167 100644 --- a/lib/units/storage/plugins/apk/task/manifest.js +++ b/lib/units/storage/plugins/apk/task/manifest.js @@ -1,7 +1,16 @@ var ApkReader = require('@devicefarmer/adbkit-apkreader') +var IpaReader = require('../../../../../util/ipareader') module.exports = function(file) { - return ApkReader.open(file.path).then(function(reader) { - return reader.readManifest() - }) + if(file.path.endsWith('.apk')){ + return ApkReader.open(file.path).then(function(reader) { + return reader.readManifest() + }) + } + else if(file.path.endsWith('.ipa')){ + var ipaReader = new IpaReader(file.path) + return ipaReader.ReadInfoPlist().then(function(res){ + return res + }) + } } diff --git a/lib/units/storage/temp.js b/lib/units/storage/temp.js index 7e770f52d..aafdc0e2b 100644 --- a/lib/units/storage/temp.js +++ b/lib/units/storage/temp.js @@ -83,6 +83,11 @@ module.exports = function(options) { }) }) }) + + function get_file_extension(source) { + var typestr = source.toLowerCase().substring(source.lastIndexOf('.') + 1) + return typestr + } app.post('/s/upload/:plugin', function(req, res) { var form = new formidable.IncomingForm({ @@ -96,7 +101,9 @@ module.exports = function(options) { file.isAab = true } var md5 = crypto.createHash('md5') - file.name = md5.update(file.name).digest('hex') + var extension = get_file_extension(file.name) + file.name = md5.update(file.name).digest('hex') + '.' + extension + log.info("Saving with extension ", file.name ) }) Promise.promisify(form.parse, form)(req) .spread(function(fields, files) { @@ -104,22 +111,25 @@ module.exports = function(options) { var file = files[field] log.info('Uploaded "%s" to "%s"', file.name, file.path) return { - field: field - , id: storage.store(file) - , name: file.name - , path: file.path - , isAab: file.isAab + field: field, + id: storage.store(file), + name: file.name, + path: file.path, + isAab: ( file.isAab || false ) } }) }) .then(function(storedFiles) { - return Promise.all(storedFiles.map(function(file) { - return bundletool({ - bundletoolPath: options.bundletoolPath - , keystore: options.keystore - , file: file - }) - }) + return Promise.all( + storedFiles.map( + function(file) { + return bundletool( { + bundletoolPath: options.bundletoolPath, + keystore: options.keystore, + file: file + } ) + } + ) ) }) .then(function(storedFiles) { diff --git a/lib/units/websocket/index.js b/lib/units/websocket/index.js index b7071d034..d82aa30d8 100644 --- a/lib/units/websocket/index.js +++ b/lib/units/websocket/index.js @@ -1001,6 +1001,56 @@ module.exports = function(options) { ) ]) }) + .on('alert.text', function(channel, responseChannel) { + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction( + responseChannel + , new wire.AlertTextMessage() + ) + ]) + }) + .on('alert.buttons', function(channel, responseChannel) { + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction( + responseChannel + , new wire.AlertButtonsMessage() + ) + ]) + }) + .on('alert.accept', function(channel, responseChannel, data) { + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction( + responseChannel + , new wire.AlertAcceptMessage(data) + ) + ]) + }) + .on('alert.dismiss', function(channel, responseChannel, data) { + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction( + responseChannel + , new wire.AlertDismissMessage(data) + ) + ]) + }) + .on('input.wheel', function(channel, responseChannel, data) { + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction( + responseChannel + , new wire.InputWheelMessage(data) + ) + ]) + }) }) .finally(function() { // Clean up all listeners and subscriptions diff --git a/lib/util/download.js b/lib/util/download.js index 372345e82..83663e469 100644 --- a/lib/util/download.js +++ b/lib/util/download.js @@ -1,18 +1,27 @@ var fs = require('fs') var Promise = require('bluebird') -var request = require('request') +var request = require('request').defaults({ rejectUnauthorized: false }) var progress = require('request-progress') var temp = require('temp') +var logger = require('./logger') +var log = logger.createLogger('util:download') module.exports = function download(url, options) { var resolver = Promise.defer() var path = temp.path(options) + if(url.endsWith('.apk')){ + path += '.apk' + } + else if(url.endsWith('.ipa')){ + path += '.ipa' + } + log.info("Downloading " + url + " to " + path ) function errorListener(err) { resolver.reject(err) } - + function progressListener(state) { if (state.total !== null) { resolver.progress({ diff --git a/lib/util/ipareader.js b/lib/util/ipareader.js new file mode 100644 index 000000000..bcf876693 --- /dev/null +++ b/lib/util/ipareader.js @@ -0,0 +1,90 @@ +'use strict' + +const Promise = require('bluebird') +var bplist = require('bplist') +var plist = require('plist') +var fs = require('fs') +var unzipper = require('unzipper') +var os = require('os') +var util = require('util') +var execSync = require('child_process').execSync; +var EventEmitter = require('eventemitter3') +var logger = require('./logger') +var log = logger.createLogger('util:ipareader') + +function IpaReader(filepath) { + EventEmitter.call(this) + this.file = filepath + this.cachedir = os.tmpdir()+'/ipa' +} + +util.inherits(IpaReader, EventEmitter) + +IpaReader.prototype.parsePlist = function(){ + var manifest = {} + + var destdir = this.cachedir+'/Payload' + var dirs = fs.readdirSync(destdir) + destdir = util.format("%s/%s",destdir,dirs[0]) + var destfile = destdir+'/Info.plist' + var content = fs.readFileSync(destfile) + return new Promise((resolve,reject)=>{ + var process = function(err,result){ + if(err){ + return reject(err) + } + manifest = result[0] + manifest.package = result[0].CFBundleIdentifier + manifest.versionCode = parseInt(result[0].CFBundleInfoDictionaryVersion) + manifest.versionName = result[0].CFBundleShortVersionString + return resolve(manifest) + }; + + console.log( typeof( content ) ); + var firstSix = content.toString( 'ascii', 0, 6 ); + if( firstSix == "bplist" ) { + bplist.parseBuffer( content, process ) + } + else { + var data = plist.parse( content.toString('utf-8') ); + process( 0, [data] ); + } + } ) +} + +IpaReader.prototype.UnzipIpa = function(){ + this.cachedir = os.tmpdir()+'/ipa' + if(fs.existsSync(this.cachedir)){ + var cmd = 'rm -rf '+this.cachedir + console.log(cmd) + execSync(cmd,{}); + } + fs.mkdirSync(this.cachedir) + + log.info("Extracting " + this.file + " to " + this.cachedir ); + + return new Promise((resolve,reject)=>{ + var extractor = unzipper.Extract({ + path: this.cachedir + }); + extractor.on('error', function(err) { + throw err; + }); + extractor.promise().then(function() { + return resolve() + }); + fs.createReadStream( this.file ).pipe( extractor ) + }) +} + +IpaReader.prototype.ReadInfoPlist = function(){ + return new Promise((resolve,reject)=>{ + this.UnzipIpa().then(()=>{ + this.parsePlist().then(function(res){ + return resolve(res) + }) + }) + }) +} + +module.exports = IpaReader \ No newline at end of file diff --git a/lib/wire/wire.proto b/lib/wire/wire.proto index 3bc8cdf5d..ceceffe6a 100644 --- a/lib/wire/wire.proto +++ b/lib/wire/wire.proto @@ -90,6 +90,11 @@ enum MessageType { GroupChangeMessage = 1205; UserChangeMessage = 1206; DeviceChangeMessage = 1207; + AlertTextMessage = 1300; + AlertButtonsMessage = 1301; + AlertAcceptMessage = 1302; + AlertDismissMessage = 1303; + InputWheelMessage = 1304; } message UpdateAccessTokenMessage { @@ -749,3 +754,21 @@ message RotationEvent { required string serial = 1; required int32 rotation = 2; } + +message AlertTextMessage { +} + +message AlertButtonsMessage { +} + +message AlertAcceptMessage { + required string name = 1; +} + +message AlertDismissMessage { + required string name = 1; +} + +message InputWheelMessage { + required int32 dist = 1; +} diff --git a/package.json b/package.json index a6b2cbcad..4959f1b75 100644 --- a/package.json +++ b/package.json @@ -29,49 +29,64 @@ "duplicate-arguments-array": false }, "scripts": { - "test": "gulp test", - "prepublish": "bower install && not-in-install && gulp build || in-install" + "test": "gulp test" }, "dependencies": { "@slack/client": "^3.5.4", "@devicefarmer/adbkit": "^2.11.2", "@devicefarmer/adbkit-apkreader": "^3.2.1", "@devicefarmer/adbkit-monkey": "^1.0.1", + "@devicefarmer/minicap-prebuilt": "^2.4.0", + "@devicefarmer/minitouch-prebuilt": "^1.3.0", + "@devicefarmer/stf-appstore-db": "^1.0.0", + "@devicefarmer/stf-browser-db": "^1.0.2", + "@devicefarmer/stf-device-db": "^1.2.0", + "@devicefarmer/stf-syrup": "^1.0.1", + "@devicefarmer/stf-wiki": "^1.0.0", + "@julusian/jpeg-turbo": "^0.5.4", + "@slack/client": "^3.5.4", "android-device-list": "^1.2.1", "aws-sdk": "^2.4.13", "basic-auth": "^1.0.3", "bluebird": "^2.10.1", "body-parser": "^1.13.3", + "bplist": "0.0.4", "bufferutil": "^1.2.1", "chalk": "~1.1.1", "compression": "^1.5.2", "cookie-session": "^2.0.0-alpha.1", "csurf": "^1.7.0", + "d3": "~5.16.0", "debug": "^2.2.0", + "dom-cascade": "git+https://github.com/nanoscopic/DomCascade.git#1.0.3", + "draggabilly": "~2.3.0", + "epoch-charting": "~0.8.4", "eventemitter3": "^1.2.0", "express": "^4.14.0", "express-validator": "^2.20.8", "file-saver": "1.3.3", + "fontsource-lato": "~2.1.4", "formidable": "^1.2.0", "gm": "^1.23.0", "hipchatter": "^0.3.1", "http-proxy": "^1.11.2", "in-publish": "^2.0.0", - "@julusian/jpeg-turbo": "^0.5.4", "jws": "^3.1.0", "ldapjs": "^1.0.0", "lodash": "^4.14.2", "markdown-serve": "^0.8.0", + "jquery": "~3.5.1", + "micromodal": "~0.4.6", "mime": "^1.3.4", - "@devicefarmer/minicap-prebuilt": "^2.4.0", "minimatch": "^3.0.3", - "@devicefarmer/minitouch-prebuilt": "^1.3.0", + "minlib": "git+https://github.com/nanoscopic/minlib.git#1.0.2", "my-local-ip": "^1.0.0", "openid": "^2.0.1", "passport": "^0.4.1", "passport-oauth2": "^1.1.2", "passport-saml": "^0.15.0", "please-update-dependencies": "^2.0.0", + "plist": "3.0.1", "protobufjs": "^3.8.2", "proxy-addr": "^1.0.10", "pug": "^2.0.0-beta4", @@ -82,16 +97,13 @@ "serve-favicon": "^2.2.0", "serve-static": "^1.9.2", "socket.io": "^2.0.3", + "spin.js": "~2.0.2", "split": "^1.0.0", - "@devicefarmer/stf-appstore-db": "^1.0.0", - "@devicefarmer/stf-browser-db": "^1.0.2", - "@devicefarmer/stf-device-db": "^1.2.0", - "@devicefarmer/stf-syrup": "^1.0.1", - "@devicefarmer/stf-wiki": "^1.0.0", "swagger-express-mw": "^0.7.0", "swagger-tools": "^0.10.3", "temp": "^0.8.1", "transliteration": "^1.1.6", + "unzipper": "~0.10.11", "url-join": "1.1.0", "utf-8-validate": "^1.2.1", "uuid": "^3.0.0", @@ -105,27 +117,25 @@ "bower": "^1.8.8", "chai": "^3.4.1", "css-loader": "^0.23.1", - "del": "^2.0.1", + "del": "^5.1.0", "eslint": "^3.2.2", "event-stream": "^3.3.2", "exports-loader": "^0.7.0", - "extract-text-webpack-plugin": "^1.0.1", - "file-loader": "^0.9.0", + "extract-text-webpack-plugin": "^3.0.2", "fs-extra": "^8.1.0", - "gulp": "^3.8.11", + "gulp": "^4.0.2", "gulp-angular-gettext": "^2.1.0", - "gulp-eslint": "^3.0.1", - "gulp-jsonlint": "^1.0.2", - "gulp-protractor": "^3.0.0", - "gulp-pug": "^3.0.4", - "gulp-run": "^1.6.12", + "gulp-eslint": "^6.0.0", + "gulp-jsonlint": "^1.3.2", + "gulp-protractor": "^4.1.1", + "gulp-pug": "^4.0.1", + "gulp-run": "^1.7.1", "gulp-util": "^3.0.7", "html-loader": "^0.5.5", "http-https": "^1.0.0", "imports-loader": "^0.8.0", "jasmine-core": "^2.4.1", "jasmine-reporters": "^2.3.2", - "json-loader": "^0.5.4", "karma": "^1.7.1", "karma-chrome-launcher": "^2.2.0", "karma-firefox-launcher": "^1.0.0", @@ -137,15 +147,13 @@ "karma-safari-launcher": "^1.0.0", "karma-webpack": "^1.8.0", "less": "^2.4.0", - "less-loader": "^2.2.2", "memory-fs": "^0.3.0", "node-libs-browser": "^1.0.0", - "node-sass": "^4.13.1", + "node-sass": "^4.14.1", "phantomjs-prebuilt": "^2.1.11", "protractor": "^5.4.1", "protractor-html-reporter-2": "1.0.4", "raw-loader": "^0.5.1", - "sass-loader": "^4.0.0", "script-loader": "^0.7.0", "sinon": "^1.17.2", "sinon-chai": "^2.7.0", @@ -153,9 +161,26 @@ "style-loader": "^0.13.0", "template-html-loader": "^0.0.3", "then-jade": "^2.4.1", - "url-loader": "^0.5.7", - "webpack": "^1.12.11", - "webpack-dev-server": "^3.1.11" + "webpack": "^4.43.0", + "webpack-dev-server": "^3.11.0", + "file-loader": "^6.0.0", + "less-loader": "5.x.x", + "sass-loader": "^9.0.2", + "url-loader": "^4.1.0", + "typescript": "^3.9.7", + "ts-loader": "^8.0.1", + "node-unzip-2": "0.2.8", + "ng-annotate-patched": "1.12.0", + "ng-annotate": "1.2.2", + "ng-annotate-loader": "0.7.0", + "@babel/cli": "7.10.5", + "@babel/core": "7.10.5", + "@babel/node": "7.10.5", + "@babel/preset-env": "7.10.4", + "@babel/register": "7.10.5", + "@babel/plugin-proposal-class-properties": "7.10.4", + "@babel/plugin-proposal-private-methods": "7.10.4", + "babel-loader": "8.1.0" }, "engines": { "node": ">= 6.9" diff --git a/res/app/app.js b/res/app/app.js index f5073847e..bcfcf53ef 100644 --- a/res/app/app.js +++ b/res/app/app.js @@ -1,17 +1,18 @@ -/** -* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0 -**/ +// Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0 + +import * as d3 from "d3"; require.ensure([], function(require) { require('angular') require('angular-route') require('angular-touch') - + require('angular-hotkeys') + angular.module('app', [ 'ngRoute', 'ngTouch', require('gettext').name, - require('angular-hotkeys').name, + 'cfp.hotkeys', require('./layout').name, require('./device-list').name, require('./group-list').name, diff --git a/res/app/components/stf/common-ui/table/index.js b/res/app/components/stf/common-ui/table/index.js index eb1056f67..7c71079df 100644 --- a/res/app/components/stf/common-ui/table/index.js +++ b/res/app/components/stf/common-ui/table/index.js @@ -1,5 +1,5 @@ require('./table.css') -require('script!ng-table/dist/ng-table') +require('script-loader!ng-table/dist/ng-table') module.exports = angular.module('stf/common-ui/table', [ 'ngTable' diff --git a/res/app/components/stf/control/control-service.js b/res/app/components/stf/control/control-service.js index a274030a7..c0285096e 100644 --- a/res/app/components/stf/control/control-service.js +++ b/res/app/components/stf/control/control-service.js @@ -3,6 +3,7 @@ module.exports = function ControlServiceFactory( , $http , socket , TransactionService +, TransactionError , $rootScope , gettext , KeycodesMapped @@ -12,10 +13,12 @@ module.exports = function ControlServiceFactory( function ControlService(target, channel) { function sendOneWay(action, data) { + console.log("sendOneWay",action,data); socket.emit(action, channel, data) } function sendTwoWay(action, data) { + console.log("sendTwoWay",action,data); var tx = TransactionService.create(target) socket.emit(action, channel, tx.channel, data) return tx.promise @@ -292,7 +295,51 @@ module.exports = function ControlServiceFactory( this.getWifiStatus = function() { return sendTwoWay('wifi.get') } - + + this.getAlertInfo = function() { + return new Promise( function( resolve, reject ) { + sendTwoWay('alert.text',{}).then( function( res ) { + console.log( 'alert.text result', res ); + sendTwoWay('alert.buttons',{}).then( function( res2 ) { + console.log( 'alert.buttons results', res2 ); + resolve( [ res, res2 ] ); + } ) + .catch(TransactionError, function() { + throw new Error('alert.buttons failure') + }); + } ) + .catch(TransactionError, function() { + throw new Error('alert.text failure') + }); + } ); + } + + this.alertAccept = function( name ) { + return new Promise( function( resolve, reject ) { + sendTwoWay('alert.accept',{ name: name }) + .then( function( res ) { + console.log( 'alert.accept result', res ); + resolve( res ); + } ) + .catch(TransactionError, function() { + reject(); + }); + } ); + } + + this.wheel = function( dist ) { + return new Promise( function( resolve, reject ) { + sendTwoWay('input.wheel',{ dist: dist }) + .then( function( res ) { + console.log( 'input.wheel result', res ); + resolve( res ); + } ) + .catch(TransactionError, function() { + reject(); + }); + } ); + } + window.cc = this } diff --git a/res/app/components/stf/install/install-service.js b/res/app/components/stf/install/install-service.js index 3c7cd98ee..bee0d87d5 100644 --- a/res/app/components/stf/install/install-service.js +++ b/res/app/components/stf/install/install-service.js @@ -92,7 +92,7 @@ module.exports = function InstallService( $rootScope.$broadcast('installation', installation) return StorageService.storeFile('apk', $files, { filter: function(file) { - return /\.(apk|aab)$/i.test(file.name) + return /\.(apk|aab|ipa)$/i.test(file.name) } }) .progressed(function(e) { diff --git a/res/app/components/stf/screen/screen-directive.js b/res/app/components/stf/screen/screen-directive.js index 47e0f296b..cfe12cefa 100644 --- a/res/app/components/stf/screen/screen-directive.js +++ b/res/app/components/stf/screen/screen-directive.js @@ -3,21 +3,21 @@ var rotator = require('./rotator') var ImagePool = require('./imagepool') module.exports = function DeviceScreenDirective( - $document -, ScalingService -, VendorUtil -, PageVisibilityService -, $timeout -, $window + $document, + ScalingService, + VendorUtil, + PageVisibilityService, + $timeout, + $window ) { return { - restrict: 'E' - , template: require('./screen.pug') - , scope: { - control: '&' - , device: '&' - } - , link: function(scope, element) { + restrict: 'E', + template: require('./screen.pug'), + scope: { + control: '&', + device: '&' + }, + link: function(scope, element) { var URL = window.URL || window.webkitURL var BLANK_IMG = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==' @@ -29,19 +29,11 @@ module.exports = function DeviceScreenDirective( var input = element.find('input') var screen = scope.screen = { - rotation: 0 - , bounds: { - x: 0 - , y: 0 - , w: 0 - , h: 0 - } + rotation: 0, + bounds: { x: 0, y: 0, w: 0, h: 0 } } - - var scaler = ScalingService.coordinator( - device.display.width - , device.display.height - ) + + var scaler = ScalingService.coordinator( device.display.width, device.display.height ) /** * SCREEN HANDLING @@ -90,9 +82,9 @@ module.exports = function DeviceScreenDirective( var frontBackRatio = devicePixelRatio / backingStoreRatio var options = { - autoScaleForRetina: true - , density: Math.max(1, Math.min(1.5, devicePixelRatio || 1)) - , minscale: 0.36 + autoScaleForRetina: true, + density: Math.max(1, Math.min(1.5, devicePixelRatio || 1)), + minscale: 0.36 } var adjustedBoundSize @@ -114,10 +106,7 @@ module.exports = function DeviceScreenDirective( sh *= f / sh } - return { - w: Math.ceil(sw) - , h: Math.ceil(sh) - } + return { w: Math.ceil(sw), h: Math.ceil(sh) } } // FIXME: element is an object HTMLUnknownElement in IE9 @@ -205,13 +194,8 @@ module.exports = function DeviceScreenDirective( ws.onmessage = (function() { var cachedScreen = { - rotation: 0 - , bounds: { - x: 0 - , y: 0 - , w: 0 - , h: 0 - } + rotation: 0, + bounds: { x: 0, y: 0, w: 0, h: 0 } } var cachedImageWidth = 0 @@ -222,7 +206,9 @@ module.exports = function DeviceScreenDirective( function applyQuirks(banner) { element[0].classList.toggle( - 'quirk-always-upright', alwaysUpright = banner.quirks.alwaysUpright) + 'quirk-always-upright', + alwaysUpright = banner.quirks.alwaysUpright + ) } function hasImageAreaChanged(img) { @@ -411,16 +397,16 @@ module.exports = function DeviceScreenDirective( // Chrome/Safari/Opera if ( // Mac | Kinesis keyboard | Karabiner | Latin key, Kana key - e.keyCode === 0 && e.keyIdentifier === 'U+0010' || + e.keyCode === 0 && e.keyIdentifier === 'U+0010' || // Mac | MacBook Pro keyboard | Latin key, Kana key - e.keyCode === 0 && e.keyIdentifier === 'U+0020' || + e.keyCode === 0 && e.keyIdentifier === 'U+0020' || // Win | Lenovo X230 keyboard | Alt+Latin key - e.keyCode === 246 && e.keyIdentifier === 'U+00F6' || + e.keyCode === 246 && e.keyIdentifier === 'U+00F6' || // Win | Lenovo X230 keyboard | Convert key - e.keyCode === 28 && e.keyIdentifier === 'U+001C' + e.keyCode === 28 && e.keyIdentifier === 'U+001C' ) { return true } @@ -573,6 +559,11 @@ module.exports = function DeviceScreenDirective( } } + function mouseWheelListener(event) { + event.preventDefault(); + control.wheel( event.deltaY ); + } + function mouseDownListener(event) { var e = event if (e.originalEvent) { @@ -593,20 +584,28 @@ module.exports = function DeviceScreenDirective( var x = e.pageX - screen.bounds.x var y = e.pageY - screen.bounds.y - var pressure = 0.5 + + var pressure = 0.5; + if( e.shiftKey ) pressure = 1; + var scaled = scaler.coords( - screen.bounds.w - , screen.bounds.h - , x - , y - , screen.rotation - ) + screen.bounds.w, + screen.bounds.h, + x, + y, + screen.rotation + ) control.touchDown(nextSeq(), 0, scaled.xP, scaled.yP, pressure) if (fakePinch) { - control.touchDown(nextSeq(), 1, 1 - scaled.xP, 1 - scaled.yP, - pressure) + control.touchDown( + nextSeq(), + 1, + 1 - scaled.xP, + 1 - scaled.yP, + pressure + ) } control.touchCommit(nextSeq()) @@ -614,16 +613,22 @@ module.exports = function DeviceScreenDirective( activateFinger(0, x, y, pressure) if (fakePinch) { - activateFinger(1, -e.pageX + screen.bounds.x + screen.bounds.w, - -e.pageY + screen.bounds.y + screen.bounds.h, pressure) + activateFinger( + 1, + -e.pageX + screen.bounds.x + screen.bounds.w, + -e.pageY + screen.bounds.y + screen.bounds.h, + pressure + ) } element.bind('mousemove', mouseMoveListener) $document.bind('mouseup', mouseUpListener) $document.bind('mouseleave', mouseUpListener) - if (lastPossiblyBuggyMouseUpEvent && - lastPossiblyBuggyMouseUpEvent.timeStamp > e.timeStamp) { + if ( + lastPossiblyBuggyMouseUpEvent && + lastPossiblyBuggyMouseUpEvent.timeStamp > e.timeStamp + ) { // We got mouseup before mousedown. See mouseUpBugWorkaroundListener // for details. mouseUpListener(lastPossiblyBuggyMouseUpEvent) @@ -654,23 +659,35 @@ module.exports = function DeviceScreenDirective( var y = e.pageY - screen.bounds.y var pressure = 0.5 var scaled = scaler.coords( - screen.bounds.w - , screen.bounds.h - , x - , y - , screen.rotation - ) + screen.bounds.w, + screen.bounds.h, + x, + y, + screen.rotation + ) control.touchMove(nextSeq(), 0, scaled.xP, scaled.yP, pressure) if (addGhostFinger) { - control.touchDown(nextSeq(), 1, 1 - scaled.xP, 1 - scaled.yP, pressure) + control.touchDown( + nextSeq(), + 1, + 1 - scaled.xP, + 1 - scaled.yP, + pressure + ) } else if (deleteGhostFinger) { control.touchUp(nextSeq(), 1) } else if (fakePinch) { - control.touchMove(nextSeq(), 1, 1 - scaled.xP, 1 - scaled.yP, pressure) + control.touchMove( + nextSeq(), + 1, + 1 - scaled.xP, + 1 - scaled.yP, + pressure + ) } control.touchCommit(nextSeq()) @@ -681,8 +698,12 @@ module.exports = function DeviceScreenDirective( deactivateFinger(1) } else if (fakePinch) { - activateFinger(1, -e.pageX + screen.bounds.x + screen.bounds.w, - -e.pageY + screen.bounds.y + screen.bounds.h, pressure) + activateFinger( + 1, + -e.pageX + screen.bounds.x + screen.bounds.w, + -e.pageY + screen.bounds.y + screen.bounds.h, + pressure + ) } } @@ -834,12 +855,12 @@ module.exports = function DeviceScreenDirective( var y = touch.pageY - screen.bounds.y var pressure = touch.force || 0.5 var scaled = scaler.coords( - screen.bounds.w - , screen.bounds.h - , x - , y - , screen.rotation - ) + screen.bounds.w, + screen.bounds.h, + x, + y, + screen.rotation + ) slotted[touch.identifier] = slot control.touchDown(nextSeq(), slot, scaled.xP, scaled.yP, pressure) @@ -868,12 +889,12 @@ module.exports = function DeviceScreenDirective( var y = touch.pageY - screen.bounds.y var pressure = touch.force || 0.5 var scaled = scaler.coords( - screen.bounds.w - , screen.bounds.h - , x - , y - , screen.rotation - ) + screen.bounds.w, + screen.bounds.h, + x, + y, + screen.rotation + ) control.touchMove(nextSeq(), slot, scaled.xP, scaled.yP, pressure) activateFinger(slot, x, y, pressure) @@ -928,6 +949,7 @@ module.exports = function DeviceScreenDirective( element.on('touchstart', touchStartListener) element.on('mousedown', mouseDownListener) element.on('mouseup', mouseUpBugWorkaroundListener) + element.on('wheel', mouseWheelListener) createSlots() })() diff --git a/res/app/components/stf/storage/storage-service.js b/res/app/components/stf/storage/storage-service.js index 7c3a1f76b..97ebe8de9 100644 --- a/res/app/components/stf/storage/storage-service.js +++ b/res/app/components/stf/storage/storage-service.js @@ -18,6 +18,9 @@ module.exports = function StorageServiceFactory($http, $upload) { var input = options.filter ? files.filter(options.filter) : files if (input.length) { + if(input.indexOf('.ipa')!=-1) + type='ipa' + $upload.upload({ url: '/s/upload/' + type , method: 'POST' diff --git a/res/app/control-panes/advanced/advanced.pug b/res/app/control-panes/advanced/advanced.pug index b277b7b38..9d548a47e 100644 --- a/res/app/control-panes/advanced/advanced.pug +++ b/res/app/control-panes/advanced/advanced.pug @@ -11,3 +11,5 @@ .col-md-6 div(ng-include='"control-panes/advanced/maintenance/maintenance.pug"') + +div(ng-include='"control-panes/advanced/input/alert-dialog.pug"') diff --git a/res/app/control-panes/advanced/index.js b/res/app/control-panes/advanced/index.js index 78dc569a1..7a79dff49 100644 --- a/res/app/control-panes/advanced/index.js +++ b/res/app/control-panes/advanced/index.js @@ -1,4 +1,5 @@ require('./advanced.css') +require('./input/alert-dialog.css') module.exports = angular.module('stf.advanced', [ require('./input').name, diff --git a/res/app/control-panes/advanced/input/alert-dialog.css b/res/app/control-panes/advanced/input/alert-dialog.css new file mode 100644 index 000000000..dd5bb09ba --- /dev/null +++ b/res/app/control-panes/advanced/input/alert-dialog.css @@ -0,0 +1,106 @@ +/**************************\ + Basic Modal Styles +\**************************/ + + +.modal { + display: none; +} + +.modal.is-open { + display: block; +} + +.modal { + font-family: -apple-system,BlinkMacSystemFont,avenir next,avenir,helvetica neue,helvetica,ubuntu,roboto,noto,segoe ui,arial,sans-serif; +} + +.modal__overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0,0,0,0.6); + display: flex; + justify-content: center; + align-items: center; +} + +.modal__container { + background-color: #fff; + padding: 30px; + max-width: 500px; + max-height: 100vh; + border-radius: 4px; + overflow-y: auto; + box-sizing: border-box; +} + +.modal__header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.modal__title { + margin-top: 0; + margin-bottom: 0; + font-weight: 600; + font-size: 1.25rem; + line-height: 1.25; + color: #00449e; + box-sizing: border-box; +} + +.modal__close { + background: transparent; + border: 0; +} + +.modal__header .modal__close:before { content: "\2715"; } + +.modal__content { + margin-top: 2rem; + margin-bottom: 2rem; + line-height: 1.5; + color: rgba(0,0,0,.8); +} + +.modal__btn { + font-size: .875rem; + padding-left: 1rem; + padding-right: 1rem; + padding-top: .5rem; + padding-bottom: .5rem; + background-color: #e6e6e6; + color: rgba(0,0,0,.8); + border-radius: .25rem; + border-style: none; + border-width: 0; + cursor: pointer; + -webkit-appearance: button; + text-transform: none; + overflow: visible; + line-height: 1.15; + margin: 0; + will-change: transform; + -moz-osx-font-smoothing: grayscale; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + -webkit-transform: translateZ(0); + transform: translateZ(0); + transition: -webkit-transform .25s ease-out; + transition: transform .25s ease-out; + transition: transform .25s ease-out,-webkit-transform .25s ease-out; +} + +.modal__btn:focus, .modal__btn:hover { + -webkit-transform: scale(1.05); + transform: scale(1.05); +} + +.modal__btn-primary { + background-color: #00449e; + color: #fff; +} \ No newline at end of file diff --git a/res/app/control-panes/advanced/input/alert-dialog.pug b/res/app/control-panes/advanced/input/alert-dialog.pug new file mode 100644 index 000000000..9316da163 --- /dev/null +++ b/res/app/control-panes/advanced/input/alert-dialog.pug @@ -0,0 +1,17 @@ +#alertModal.modal.micromodal-slide(aria-hidden="true") + .modal__overlay(tabindex="-1" data-micromodal-close="") + .modal__container(role="dialog" aria-modal="true" aria-labelledby="modal-1-title") + header.modal__header + h2#alertModal-title.modal__title + | Micromodal + button.modal__close(aria-label="Close modal" data-micromodal-close="") + main#alertModal-content.modal__content + p + | Try hitting the + code tab + | key and notice how the focus stays within the modal itself. Also, + code esc + | to close modal. + footer#alertModal-footer.modal__footer + button.modal__btn.modal__btn-primary Continue + button.modal__btn(data-micromodal-close="" aria-label="Close this dialog window") Close diff --git a/res/app/control-panes/advanced/input/index.js b/res/app/control-panes/advanced/input/index.js index e6ecd193e..cff753abd 100644 --- a/res/app/control-panes/advanced/input/index.js +++ b/res/app/control-panes/advanced/input/index.js @@ -6,5 +6,8 @@ module.exports = angular.module('stf.advanced.input', [ $templateCache.put('control-panes/advanced/input/input.pug', require('./input.pug') ) + $templateCache.put('control-panes/advanced/input/alert-dialog.pug', + require('./alert-dialog.pug') + ) }]) - .controller('InputAdvancedCtrl', require('./input-controller')) + .controller('InputAdvancedCtrl', require('./input-controller').default) diff --git a/res/app/control-panes/advanced/input/input-controller.js b/res/app/control-panes/advanced/input/input-controller.js index d941424bb..ff38e9e65 100644 --- a/res/app/control-panes/advanced/input/input-controller.js +++ b/res/app/control-panes/advanced/input/input-controller.js @@ -1,6 +1,56 @@ -module.exports = function InputCtrl($scope) { +import MicroModal from 'micromodal';//require('micromodal'); +import DomCascade from 'dom-cascade'; +import MinLib from 'minlib'; +export default function InputCtrl($scope) { $scope.press = function(key) { $scope.control.keyPress(key) } + + $scope.handle_alert = function() { + MicroModal.init(); + + var dc = new DomCascade(); + var ml = new MinLib(); + + $scope.control.getAlertInfo().then( function( arr ) { + // + // + + var text = arr[0].body.value; + var parts = text.split('\n'); + var title = parts.shift(); + ml.gel('alertModal-title').innerHTML = title; + + var dcParts = []; + for( var i=0;i= 1 ? 'complete' : 'unknown' - } - log.info('Build progress %d%% (%s)', Math.floor(progress * 100), msg) } - , 1000 - )) + }, + {test: /\.pug$/, loader: 'template-html-loader?engine=jade'} + , {test: /\.html$/, loader: 'html-loader'} + , {test: /\/angular\.js$/, loader: 'exports-loader?angular'} + , {test: /angular-cookies\.js$/, loader: 'imports-loader?angular=angular'} + , {test: /angular-route\.js$/, loader: 'imports-loader?angular=angular'} + , {test: /angular-touch\.js$/, loader: 'imports-loader?angular=angular'} + , {test: /angular-animate\.js$/, loader: 'imports-loader?angular=angular'} + , {test: /angular-growl\.js$/, loader: 'imports-loader?angular=angular'} + , {test: /angular-gettext\.js$/, loader: 'imports-loader?angular=angular'} + , {test: /dialogs\.js$/, loader: 'script-loader'} + , {test: /epoch\.js$/, loader: 'imports-loader?d3=d3'} + //, {test: /\.ts$/, loader: 'ng-annotate-loader?ngAnnotate=ng-annotate-patched!ts-loader` + , { + test: /\.js$/, + use: { + loader: 'ng-annotate-loader', + options: { + ngAnnotate: 'ng-annotate-patched', + es6: true, + explicityOnly: false + } + }, + exclude: /node_modules/ + } + , { + test: /\.ts$/, + use: { + loader: 'ts-loader' + }, + exclude: /node_modules/ + } + , { + test: /\.jsx$/, + use: { + loader: 'babel-loader', + options: { + "presets": [ + "@babel/preset-env" + ], + "plugins": [ + "@babel/plugin-proposal-class-properties", + "@babel/plugin-proposal-private-methods" + ] + } + }, + exclude: /node_modules/ + } + ], + // TODO: enable when its sane + // preLoaders: [ + // { + // test: /\.js$/, + // exclude: /node_modules|bower_components/, + // loader: 'eslint-loader' + // } + // ], + noParse: [ + pathutil.resource('bower_modules') ] - } - , webpackServer: { - debug: true - , devtool: 'eval' - , stats: { - colors: true - } - } + }, + plugins: [ + new ProgressPlugin(_.throttle( + function(progress, message) { + var msg + if (message) { + msg = message + } + else { + msg = progress >= 1 ? 'complete' : 'unknown' + } + log.info('Build progress %d%% (%s)', Math.floor(progress * 100), msg) + } + , 1000 + )) + ] } diff --git a/webpackserver.config.js b/webpackserver.config.js new file mode 100644 index 000000000..adce56a22 --- /dev/null +++ b/webpackserver.config.js @@ -0,0 +1,9 @@ +module.exports = { + webpackServer: { + debug: true, + devtool: 'eval', + stats: { + colors: true + } + } +}