diff --git a/.bowerrc b/.bowerrc new file mode 100644 index 00000000..a13a285b --- /dev/null +++ b/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory": "res/bower_components" +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..8d2de3b4 --- /dev/null +++ b/package.json @@ -0,0 +1,132 @@ +{ + "name": "stf", + "description": "Smartphone Test Farm", + "keywords": [ + "adb", + "android", + "stf", + "test", + "remote" + ], + "version": "0.9.0", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/openstf/stf.git" + }, + "bin": { + "stf": "./bin/stf" + }, + "scripts": { + "test": "gulp test" + }, + "dependencies": { + "adbkit": "^2.2.1", + "adbkit-apkreader": "^1.0.0", + "adbkit-monkey": "^1.0.1", + "bluebird": "^2.9.14", + "body-parser": "^1.12.2", + "chalk": "^1.0.0", + "commander": "^2.7.1", + "compression": "^1.4.3", + "cookie-session": "^1.1.0", + "csurf": "^1.7.0", + "eventemitter3": "^0.1.6", + "express": "^4.12.3", + "express-validator": "^2.8.0", + "formidable": "^1.0.17", + "gm": "^1.17.0", + "hipchatter": "^0.2.0", + "http-proxy": "^1.9.0", + "jade": "^1.9.2", + "jws": "^3.0.0", + "ldapjs": "git+https://github.com/mcavage/node-ldapjs.git#acc1ca8f4314fd9d67561feabc8ce4c235076a5e", + "lodash": "^3.5.0", + "markdown-serve": "^0.3.2", + "mime": "^1.3.4", + "minimatch": "^2.0.4", + "my-local-ip": "^1.0.0", + "node-uuid": "^1.4.3", + "passport": "^0.2.1", + "passport-oauth2": "^1.1.2", + "protobufjs": "^3.8.2", + "proxy-addr": "^1.0.7", + "request": "^2.53.0", + "request-progress": "^0.3.1", + "rethinkdb": "^1.15.0", + "semver": "^4.3.1", + "serve-favicon": "^2.2.0", + "serve-static": "^1.9.2", + "socket.io": "1.3.5", + "split": "^0.3.3", + "stf-appstore-db": "^1.0.0", + "stf-browser-db": "^1.0.0", + "stf-device-db": "^1.0.0", + "stf-syrup": "^1.0.0", + "stf-wiki": "^1.0.0", + "temp": "^0.8.1", + "ws": "^0.7.2", + "zmq": "^2.10.0" + }, + "devDependencies": { + "async": "^1.2.1", + "bower": "^1.3.12", + "chai": "^2.1.2", + "css-loader": "^0.15.1", + "del": "^1.2.0", + "event-stream": "^3.3.0", + "exports-loader": "^0.6.2", + "extract-text-webpack-plugin": "^0.8.2", + "file-loader": "^0.8.1", + "gulp": "^3.8.11", + "gulp-angular-gettext": "^2.1.0", + "gulp-jade": "^1.0.0", + "gulp-jscs": "^1.4.0", + "gulp-jshint": "^1.9.4", + "gulp-jsonlint": "^1.0.2", + "gulp-protractor": "^0.0.12", + "gulp-util": "^3.0.4", + "html-loader": "^0.3.0", + "imports-loader": "^0.6.3", + "jasmine-core": "^2.3.4", + "jasmine-reporters": "^2.0.5", + "jshint": "^2.6.3", + "jshint-loader": "^0.8.3", + "jshint-stylish": "^2.0.0", + "json-loader": "^0.5.1", + "karma": "^0.12.32", + "karma-chrome-launcher": "^0.2.0", + "karma-firefox-launcher": "^0.1.4", + "karma-ie-launcher": "^0.2.0", + "karma-jasmine": "^0.3.5", + "karma-junit-reporter": "^0.2.2", + "karma-opera-launcher": "^0.1.0", + "karma-phantomjs-launcher": "^0.2.0", + "karma-safari-launcher": "^0.1.1", + "karma-webpack": "^1.5.0", + "less": "^2.4.0", + "less-loader": "^2.1.0", + "ng-annotate-webpack-plugin": "^0.1.2", + "node-libs-browser": "^0.5.2", + "node-sass": "^3.2.0", + "phantomjs": "^1.9.17", + "protractor": "^2.0.0", + "protractor-html-screenshot-reporter": "0.0.19", + "raw-loader": "^0.5.1", + "run-sequence": "^1.0.2", + "sass-loader": "^1.0.2", + "script-loader": "^0.6.1", + "sinon": "^1.14.1", + "sinon-chai": "^2.7.0", + "socket.io-client": "1.3.5", + "style-loader": "^0.12.3", + "template-html-loader": "^0.0.3", + "url-loader": "^0.5.5", + "webpack": "^1.10.0", + "webpack-dev-server": "^1.7.0" + }, + "engines": { + "node": ">= 0.10" + }, + "preferGlobal": true +} diff --git a/res/app/components/stf/device/enhance-device/enhance-device-service.js b/res/app/components/stf/device/enhance-device/enhance-device-service.js new file mode 100644 index 00000000..7f79fb3d --- /dev/null +++ b/res/app/components/stf/device/enhance-device/enhance-device-service.js @@ -0,0 +1,93 @@ +module.exports = function EnhanceDeviceServiceFactory($filter, AppState) { + var service = {} + + function setState(data) { + // For convenience, formulate an aggregate state property that covers + // every possible state. + data.state = 'absent' + if (data.present) { + data.state = 'present' + switch (data.status) { + case 1: + data.state = 'offline' + break + case 2: + data.state = 'unauthorized' + break + case 3: + data.state = 'preparing' + if (data.ready) { + data.state = 'ready' + if (data.using) { + data.state = 'using' + } + else { + if (data.owner) { + data.state = 'busy' + } + else { + data.state = 'available' + } + } + } + break + } + } + } + + function enhanceDevice(device) { + device.enhancedName = device.name || device.model || device.serial || 'Unknown' + device.enhancedModel = device.model || 'Unknown' + device.enhancedImage120 = '/static/app/devices/icon/x120/' + (device.image || '_default.jpg') + device.enhancedImage24 = '/static/app/devices/icon/x24/' + (device.image || '_default.jpg') + device.enhancedStateAction = $filter('statusNameAction')(device.state) + device.enhancedStatePassive = $filter('statusNamePassive')(device.state) + } + + function enhanceDeviceDetails(device) { + if (device.battery) { + device.enhancedBatteryPercentage = (device.battery.level / device.battery.scale * 100) + '%' + device.enhancedBatteryHealth = $filter('batteryHealth')(device.battery.health) + device.enhancedBatterySource = $filter('batterySource')(device.battery.source) + device.enhancedBatteryStatus = $filter('batteryStatus')(device.battery.status) + device.enhancedBatteryTemp = device.battery.temp + '°C' + } + + if (device.owner) { + device.enhancedUserProfileUrl = enhanceUserProfileUrl(device.owner.email) + device.enhancedUserName = device.owner.name || "No name" + } + } + + function enhanceUserProfileUrl(email) { + var url + var userProfileUrl = (function () { + if (AppState && AppState.config && AppState.config.userProfileUrl) { + return AppState.config.userProfileUrl + } + return null + })() + + if (userProfileUrl) { + // Using RFC 6570 URI Template specification + if (userProfileUrl && email) { + url = userProfileUrl.indexOf('{user}') !== -1 ? + userProfileUrl.replace('{user}', email) : + userProfileUrl + email; + } + } else if (email.indexOf('@') !== -1) { + url = 'mailto:' + email + } else { + url = '/!#/user/' + email + } + return url + } + + service.enhance = function (device) { + setState(device) + enhanceDevice(device) + enhanceDeviceDetails(device) + } + + return service +} diff --git a/res/app/device-list/device-list-controller.js b/res/app/device-list/device-list-controller.js new file mode 100644 index 00000000..9f84dd05 --- /dev/null +++ b/res/app/device-list/device-list-controller.js @@ -0,0 +1,192 @@ +var QueryParser = require('./util/query-parser') + +module.exports = function DeviceListCtrl( + $scope +, DeviceService +, DeviceColumnService +, GroupService +, ControlService +, SettingsService +, $location +) { + $scope.tracker = DeviceService.trackAll($scope) + $scope.control = ControlService.create($scope.tracker.devices, '*ALL') + + $scope.columnDefinitions = DeviceColumnService + + var defaultColumns = [ + { + name: 'state' + , selected: true + } + , { + name: 'model' + , selected: true + } + , { + name: 'name' + , selected: true + } + , { + name: 'serial' + , selected: false + } + , { + name: 'operator' + , selected: true + } + , { + name: 'releasedAt' + , selected: true + } + , { + name: 'version' + , selected: true + } + , { + name: 'network' + , selected: false + } + , { + name: 'display' + , selected: false + } + , { + name: 'manufacturer' + , selected: false + } + , { + name: 'sdk' + , selected: false + } + , { + name: 'abi' + , selected: false + } + , { + name: 'browser' + , selected: false + } + , { + name: 'phone' + , selected: false + } + , { + name: 'imei' + , selected: false + } + , { + name: 'iccid' + , selected: false + } + , { + name: 'batteryHealth' + , selected: false + } + , { + name: 'batterySource' + , selected: false + } + , { + name: 'batteryStatus' + , selected: false + } + , { + name: 'batteryLevel' + , selected: false + } + , { + name: 'batteryTemp' + , selected: false + } + , { + name: 'provider' + , selected: true + } + , { + name: 'notes' + , selected: true + } + , { + name: 'owner' + , selected: true + } + ] + + $scope.columns = defaultColumns + + SettingsService.bind($scope, { + target: 'columns' + , source: 'deviceListColumns' + }) + + var defaultSort = { + fixed: [ + { + name: 'state' + , order: 'asc' + } + ] + , user: [ + { + name: 'name' + , order: 'asc' + } + ] + } + + $scope.sort = defaultSort + + SettingsService.bind($scope, { + target: 'sort' + , source: 'deviceListSort' + }) + + $scope.filter = [] + + $scope.activeTabs = { + icons: true + , details: false + } + + SettingsService.bind($scope, { + target: 'activeTabs' + , source: 'deviceListActiveTabs' + }) + + $scope.toggle = function (device) { + if (device.using) { + $scope.kick(device) + } else { + $location.path('/control/' + device.serial) + } + } + + $scope.invite = function (device) { + return GroupService.invite(device).then(function () { + $scope.$digest() + }) + } + + $scope.applyFilter = function(query) { + $scope.filter = QueryParser.parse(query) + } + + $scope.search = { + deviceFilter: '', + focusElement: false + } + + $scope.focusSearch = function () { + if (!$scope.basicMode) { + $scope.search.focusElement = true + } + } + + $scope.reset = function () { + $scope.search.deviceFilter = '' + $scope.filter = [] + $scope.sort = defaultSort + $scope.columns = defaultColumns + } +}