From c33867809405b41cb364fc9852fbdbe100230ebb Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Fri, 13 Feb 2015 10:44:00 -0800 Subject: [PATCH] Merge remote-tracking branch 'origin/sean-polish' Conflicts: src/Radial.react.js src/Setup.react.js styles/radial.less --- package.json | 2 +- src/ContainerDetailsHeader.react.js | 5 +- src/ContainerDetailsSubheader.react.js | 187 ++++++ ....react.js => ContainerDetailsbak.react.js} | 115 ++-- src/ContainerHome.react.js | 167 +++--- src/ContainerHomeFolders.react.js | 48 ++ src/ContainerHomeLogs.react.js | 64 ++ src/ContainerHomePreview.react.js | 97 ++++ src/ContainerListItem.react.js | 2 +- src/ContainerListNewItem.react.js | 2 + src/ContainerLogs.react.js | 57 ++ src/ContainerSettings.react.js | 53 ++ src/ContainerSettingsGeneral.react.js | 234 ++++++++ src/ContainerSettingsPorts.react.js | 85 +++ src/ContainerSettingsVolumes.react.js | 82 +++ src/ContainerUtil.js | 2 +- src/Containers.react.js | 6 +- src/NewContainer.react.js | 32 +- src/Radial.react.js | 3 +- src/Routes.js | 16 +- styles/containers.less | 549 +++++++++++------- styles/header.less | 2 +- styles/radial.less | 21 +- styles/theme.less | 16 +- 24 files changed, 1482 insertions(+), 365 deletions(-) create mode 100644 src/ContainerDetailsSubheader.react.js rename src/{ContainerDetails.react.js => ContainerDetailsbak.react.js} (88%) create mode 100644 src/ContainerHomeFolders.react.js create mode 100644 src/ContainerHomeLogs.react.js create mode 100644 src/ContainerHomePreview.react.js create mode 100644 src/ContainerLogs.react.js create mode 100644 src/ContainerSettings.react.js create mode 100644 src/ContainerSettingsGeneral.react.js create mode 100644 src/ContainerSettingsPorts.react.js create mode 100644 src/ContainerSettingsVolumes.react.js diff --git a/package.json b/package.json index dbcf32a0..07ed9db5 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "release": "gulp release", "release:beta": "gulp release --beta", "preinstall": "./deps", - "lint": "jsxhint src/**/* && jsxhint browser/**/*" + "lint": "jsxhint src && jsxhint browser" }, "licenses": [ { diff --git a/src/ContainerDetailsHeader.react.js b/src/ContainerDetailsHeader.react.js index 187d9438..ded2816d 100644 --- a/src/ContainerDetailsHeader.react.js +++ b/src/ContainerDetailsHeader.react.js @@ -3,7 +3,10 @@ var React = require('react/addons'); var ContainerDetailsHeader = React.createClass({ render: function () { var state; - if (this.props.container.State.Running) { + if (!this.props.container) { + return false; + } + if (this.props.container.State.Running && !this.props.container.State.Paused && !this.props.container.State.Restarting) { state = RUNNING; } else if (this.props.container.State.Restarting) { state = RESTARTING; diff --git a/src/ContainerDetailsSubheader.react.js b/src/ContainerDetailsSubheader.react.js new file mode 100644 index 00000000..28bd6182 --- /dev/null +++ b/src/ContainerDetailsSubheader.react.js @@ -0,0 +1,187 @@ +var _ = require('underscore'); +var $ = require('jquery'); +var React = require('react/addons'); +var exec = require('exec'); +var path = require('path'); +var ContainerStore = require('./ContainerStore'); +var ContainerUtil = require('./ContainerUtil'); +var boot2docker = require('./Boot2Docker'); +var RetinaImage = require('react-retina-image'); +var Router = require('react-router'); + +var ContainerDetailsSubheader = React.createClass({ + mixins: [Router.State, Router.Navigation], + getInitialState: function () { + return { + defaultPort: null + }; + }, + componentWillReceiveProps: function () { + this.init(); + }, + componentDidMount: function () { + this.init(); + }, + init: function () { + this.setState({ + currentRoute: _.last(this.getRoutes()).name + }); + var container = ContainerStore.container(this.getParams().name); + if (!container) { + return; + } + var ports = ContainerUtil.ports(container); + var webPorts = ['80', '8000', '8080', '3000', '5000', '2368']; + this.setState({ + ports: ports, + defaultPort: _.find(_.keys(ports), function (port) { + return webPorts.indexOf(port) !== -1; + }) + }); + }, + disableRun: function () { + if (!this.props.container) { + return false; + } + return (!this.props.container.State.Running || !this.state.defaultPort); + }, + disableRestart: function () { + if (!this.props.container) { + return false; + } + return (this.props.container.State.Downloading || this.props.container.State.Restarting); + }, + disableTerminal: function () { + if (!this.props.container) { + return false; + } + return (!this.props.container.State.Running); + }, + disableTab: function () { + if (!this.props.container) { + return false; + } + return (this.props.container.State.Downloading); + }, + showHome: function () { + if (!this.disableTab()) { + this.transitionTo('containerHome', {name: this.getParams().name}); + } + }, + showLogs: function () { + if (!this.disableTab()) { + this.transitionTo('containerLogs', {name: this.getParams().name}); + } + }, + showSettings: function () { + if (!this.disableTab()) { + this.transitionTo('containerSettings', {name: this.getParams().name}); + } + }, + handleRun: function () { + if (this.state.defaultPort && !this.disableRun()) { + exec(['open', this.state.ports[this.state.defaultPort].url], function (err) { + if (err) { throw err; } + }); + } + }, + handleRestart: function () { + if (!this.disableRestart()) { + ContainerStore.restart(this.props.container.Name, function (err) { + console.log(err); + }); + } + }, + handleTerminal: function () { + if (!this.disableTerminal()) { + var container = this.props.container; + var terminal = path.join(process.cwd(), 'resources', 'terminal'); + var cmd = [terminal, boot2docker.command().replace(/ /g, '\\\\\\\\ ').replace(/\(/g, '\\\\\\\\(').replace(/\)/g, '\\\\\\\\)'), 'ssh', '-t', 'sudo', 'docker', 'exec', '-i', '-t', container.Name, 'sh']; + exec(cmd, function (stderr, stdout, code) { + console.log(stderr); + console.log(stdout); + if (code) { + console.log(stderr); + } + }); + } + }, + handleItemMouseEnterRun: function () { + var $action = $(this.getDOMNode()).find('.action .run'); + $action.css("visibility", "visible"); + }, + handleItemMouseLeaveRun: function () { + var $action = $(this.getDOMNode()).find('.action .run'); + $action.css("visibility", "hidden"); + }, + handleItemMouseEnterRestart: function () { + var $action = $(this.getDOMNode()).find('.action .restart'); + $action.css("visibility", "visible"); + }, + handleItemMouseLeaveRestart: function () { + var $action = $(this.getDOMNode()).find('.action .restart'); + $action.css("visibility", "hidden"); + }, + handleItemMouseEnterTerminal: function () { + var $action = $(this.getDOMNode()).find('.action .terminal'); + $action.css("visibility", "visible"); + }, + handleItemMouseLeaveTerminal: function () { + var $action = $(this.getDOMNode()).find('.action .terminal'); + $action.css("visibility", "hidden"); + }, + render: function () { + var runActionClass = React.addons.classSet({ + action: true, + disabled: this.disableRun() + }); + var restartActionClass = React.addons.classSet({ + action: true, + disabled: this.disableRestart() + }); + var terminalActionClass = React.addons.classSet({ + action: true, + disabled: this.disableTerminal() + }); + var tabHomeClasses = React.addons.classSet({ + 'tab': true, + 'active': this.state.currentRoute === 'containerHome', + disabled: this.disableTab() + }); + var tabLogsClasses = React.addons.classSet({ + 'tab': true, + 'active': this.state.currentRoute === 'containerLogs', + disabled: this.disableTab() + }); + var tabSettingsClasses = React.addons.classSet({ + 'tab': true, + 'active': this.state.currentRoute && (this.state.currentRoute.indexOf('containerSettings') >= 0), + disabled: this.disableTab() + }); + return ( +
+
+
+ + Run +
+
+ + Restart +
+
+ + Terminal +
+
+
+ Home + Logs + Settings +
+
+ ); + } +}); + +module.exports = ContainerDetailsSubheader; diff --git a/src/ContainerDetails.react.js b/src/ContainerDetailsbak.react.js similarity index 88% rename from src/ContainerDetails.react.js rename to src/ContainerDetailsbak.react.js index e8beede0..049f41b6 100644 --- a/src/ContainerDetails.react.js +++ b/src/ContainerDetailsbak.react.js @@ -18,7 +18,7 @@ var Radial = require('./Radial.react'); var _oldHeight = 0; -var ContainerDetails = React.createClass({ +var ContainerDetailsbak = React.createClass({ mixins: [Router.State, Router.Navigation], PAGE_HOME: 'home', PAGE_LOGS: 'logs', @@ -94,15 +94,32 @@ var ContainerDetails = React.createClass({ }); } }, + disableRun: function () { + return (!this.props.container.State.Running || !this.state.defaultPort); + }, + disableRestart: function () { + return (this.props.container.State.Downloading || this.props.container.State.Restarting); + }, + disableTerminal: function () { + return (!this.props.container.State.Running); + }, + disableTab: function () { + return (this.props.container.State.Downloading); + }, showHome: function () { - this.setState({ - page: this.PAGE_HOME - }); + if (!this.disableTab()) { + /*this.setState({ + page: this.PAGE_HOME + });*/ + this.transitionTo('containerHome', {name: this.getParams().name}); + } }, showLogs: function () { - this.setState({ - page: this.PAGE_LOGS - }); + if (!this.disableTab()) { + this.setState({ + page: this.PAGE_LOGS + }); + } }, showPorts: function () { this.setState({ @@ -115,20 +132,40 @@ var ContainerDetails = React.createClass({ }); }, showSettings: function () { - this.setState({ - page: this.PAGE_SETTINGS - }); + if (!this.disableTab()) { + this.setState({ + page: this.PAGE_SETTINGS + }); + } }, - handleView: function () { - console.log('CLICKED'); - console.log(this.state.ports); - console.log(this.state.defaultPort); - if (this.state.defaultPort) { + handleRun: function () { + if (this.state.defaultPort && !this.disableRun()) { exec(['open', this.state.ports[this.state.defaultPort].url], function (err) { if (err) { throw err; } }); } }, + handleRestart: function () { + if (!this.disableRestart()) { + ContainerStore.restart(this.props.container.Name, function (err) { + console.log(err); + }); + } + }, + handleTerminal: function () { + if (!this.disableTerminal()) { + var container = this.props.container; + var terminal = path.join(process.cwd(), 'resources', 'terminal'); + var cmd = [terminal, boot2docker.command().replace(/ /g, '\\\\\\\\ ').replace(/\(/g, '\\\\\\\\(').replace(/\)/g, '\\\\\\\\)'), 'ssh', '-t', 'sudo', 'docker', 'exec', '-i', '-t', container.Name, 'sh']; + exec(cmd, function (stderr, stdout, code) { + console.log(stderr); + console.log(stdout); + if (code) { + console.log(stderr); + } + }); + } + }, handleViewLink: function (url) { exec(['open', url], function (err) { if (err) { throw err; } @@ -171,23 +208,6 @@ var ContainerDetails = React.createClass({ if (err) { throw err; } }); }, - handleRestart: function () { - ContainerStore.restart(this.props.container.Name, function (err) { - console.log(err); - }); - }, - handleTerminal: function () { - var container = this.props.container; - var terminal = path.join(process.cwd(), 'resources', 'terminal'); - var cmd = [terminal, boot2docker.command().replace(/ /g, '\\\\\\\\ ').replace(/\(/g, '\\\\\\\\(').replace(/\)/g, '\\\\\\\\)'), 'ssh', '-t', 'sudo', 'docker', 'exec', '-i', '-t', container.Name, 'sh']; - exec(cmd, function (stderr, stdout, code) { - console.log(stderr); - console.log(stdout); - if (code) { - console.log(stderr); - } - }); - }, handleSaveContainerName: function () { var newName = $('#input-container-name').val(); if (newName === this.props.container.Name) { @@ -515,19 +535,19 @@ var ContainerDetails = React.createClass({ var tabHomeClasses = React.addons.classSet({ 'tab': true, 'active': this.state.page === this.PAGE_HOME, - disabled: this.props.container.State.Downloading + disabled: this.disableTab() }); var tabLogsClasses = React.addons.classSet({ 'tab': true, 'active': this.state.page === this.PAGE_LOGS, - disabled: this.props.container.State.Downloading + disabled: this.disableTab() }); var tabSettingsClasses = React.addons.classSet({ 'tab': true, 'active': this.state.page === this.PAGE_SETTINGS, - disabled: this.props.container.State.Downloading + disabled: this.disableTab() }); /*var ports = _.map(_.pairs(self.state.ports), function (pair, index, list) { @@ -572,20 +592,35 @@ var ContainerDetails = React.createClass({ ); }*/ + var runActionClass = React.addons.classSet({ + action: true, + disabled: this.disableRun() + }); + + var restartActionClass = React.addons.classSet({ + action: true, + disabled: this.disableRestart() + }); + + var terminalActionClass = React.addons.classSet({ + action: true, + disabled: this.disableTerminal() + }); + return (
-
- +
+ Run
-
+
Restart
-
+
Terminal
@@ -602,4 +637,4 @@ var ContainerDetails = React.createClass({ } }); -module.exports = ContainerDetails; +module.exports = ContainerDetailsbak; diff --git a/src/ContainerHome.react.js b/src/ContainerHome.react.js index 72eae2ec..394a6d69 100644 --- a/src/ContainerHome.react.js +++ b/src/ContainerHome.react.js @@ -1,105 +1,130 @@ var _ = require('underscore'); var $ = require('jquery'); var React = require('react/addons'); -var RetinaImage = require('react-retina-image'); -var path = require('path'); -var exec = require('exec'); +var ContainerStore = require('./ContainerStore'); +var Router = require('react-router'); +var Radial = require('./Radial.react'); +var ContainerHomePreview = require('./ContainerHomePreview.react'); +var ContainerHomeLogs = require('./ContainerHomeLogs.react'); +var ContainerHomeFolders = require('./ContainerHomeFolders.react'); +var ContainerUtil = require('./ContainerUtil'); + +var resizeWindow = function () { + $('.left .wrapper').height(window.innerHeight - 240); + $('.right .wrapper').height(window.innerHeight / 2 - 100); +}; var ContainerHome = React.createClass({ + mixins: [Router.State, Router.Navigation], + getInitialState: function () { + return { + ports: {}, + defaultPort: null + }; + }, handleResize: function () { - $('.web-preview').height(window.innerHeight - 240); - $('.mini-logs').height(window.innerHeight / 2 - 100); - $('.folders').height(window.innerHeight / 2 - 150); + resizeWindow(); + }, + componentWillReceiveProps: function () { + this.init(); }, componentDidMount: function() { - $('.web-preview').height(window.innerHeight - 240); - $('.mini-logs').height(window.innerHeight / 2 - 100); - $('.folders').height(window.innerHeight / 2 - 150); + this.init(); + ContainerStore.on(ContainerStore.SERVER_PROGRESS_EVENT, this.updateProgress); + resizeWindow(); window.addEventListener('resize', this.handleResize); }, componentWillUnmount: function() { + ContainerStore.removeListener(ContainerStore.SERVER_PROGRESS_EVENT, this.updateProgress); window.removeEventListener('resize', this.handleResize); }, componentDidUpdate: function () { - // Scroll logs to bottom - $('.web-preview').height(window.innerHeight - 240); - $('.mini-logs').height(window.innerHeight / 2 - 100); - $('.folders').height(window.innerHeight / 2 - 150); - var parent = $('.mini-logs'); - if (parent.length) { - if (parent.scrollTop() >= this._oldHeight) { - parent.stop(); - parent.scrollTop(parent[0].scrollHeight - parent.height()); - } - this._oldHeight = parent[0].scrollHeight - parent.height(); - } + resizeWindow(); }, - handleClickFolder: function (path) { - exec(['open', path], function (err) { - if (err) { throw err; } + init: function () { + var container = ContainerStore.container(this.getParams().name); + if (!container) { + return; + } + var ports = ContainerUtil.ports(container); + var webPorts = ['80', '8000', '8080', '3000', '5000', '2368']; + this.setState({ + ports: ports, + defaultPort: _.find(_.keys(ports), function (port) { + return webPorts.indexOf(port) !== -1; + }), + progress: ContainerStore.progress(this.getParams().name) }); }, - handleClickPreview: function () { - if (this.props.defaultPort) { - exec(['open', this.props.ports[this.props.defaultPort].url], function (err) { - if (err) { throw err; } + updateProgress: function (name) { + if (name === this.getParams().name) { + this.setState({ + progress: ContainerStore.progress(name) }); } }, render: function () { - var preview; - if (this.props.defaultPort) { - preview = ( -
-

Web Preview

-
- -
Open in Browser
+ var body; + if (this.props.container && this.props.container.State.Downloading) { + if (this.state.progress) { + body = ( +
+

Downloading Image

+
-
Not showing correctly?
-
- ); - } - console.log(this.props.container.Volumes); - var self = this; - var folders = _.map(self.props.container.Volumes, function (val, key) { - var firstFolder = key.split(path.sep)[1]; - if (!val || val.indexOf(process.env.HOME) === -1) { - return; + ); } else { - return ( -
- -
{firstFolder}
+ body = ( +
+

Connecting to Docker Hub

+
); } - }); - return ( -
-
-
- {preview} -
-
-
-

Logs

-
- {this.props.logs} -
View Logs
+ } else { + if (this.state.defaultPort) { + body = ( +
+
+
+ +
+
+ +
-
-

Edit Files

-
- {folders} +
+ ); + } else { + var right; + if (_.keys(this.state.ports) > 0) { + right = ( +
+ + +
+ ); + } else { + right = ( +
+ +
+ ); + } + body = ( +
+
+
+
-
Change Folders
+ {right}
-
-
- ); + ); + } + } + return body; } }); diff --git a/src/ContainerHomeFolders.react.js b/src/ContainerHomeFolders.react.js new file mode 100644 index 00000000..3f4d45f5 --- /dev/null +++ b/src/ContainerHomeFolders.react.js @@ -0,0 +1,48 @@ +var _ = require('underscore'); +var React = require('react/addons'); +var RetinaImage = require('react-retina-image'); +var path = require('path'); +var exec = require('exec'); +var Router = require('react-router'); + +var ContainerHomeFolder = React.createClass({ + mixins: [Router.State, Router.Navigation], + handleClickFolder: function (path) { + exec(['open', path], function (err) { + if (err) { throw err; } + }); + }, + handleClickChangeFolders: function () { + this.transitionTo('containerSettingsVolumes', {name: this.getParams().name}); + }, + render: function () { + var folders; + if (this.props.container) { + var self = this; + folders = _.map(self.props.container.Volumes, function (val, key) { + var firstFolder = key.split(path.sep)[1]; + if (!val || val.indexOf(process.env.HOME) === -1) { + return; + } else { + return ( +
+ +
{firstFolder}
+
+ ); + } + }); + } + return ( +
+

Edit Files

+
+ {folders} +
+
Change Folders
+
+ ); + } +}); + +module.exports = ContainerHomeFolder; diff --git a/src/ContainerHomeLogs.react.js b/src/ContainerHomeLogs.react.js new file mode 100644 index 00000000..2e66352a --- /dev/null +++ b/src/ContainerHomeLogs.react.js @@ -0,0 +1,64 @@ +var $ = require('jquery'); +var React = require('react/addons'); +var ContainerStore = require('./ContainerStore'); +var Router = require('react-router'); + +var ContainerHomeLogs = React.createClass({ + mixins: [Router.State, Router.Navigation], + getInitialState: function () { + return { + logs: [] + }; + }, + componentWillReceiveProps: function () { + this.init(); + }, + componentDidMount: function() { + this.init(); + ContainerStore.on(ContainerStore.SERVER_LOGS_EVENT, this.updateLogs); + }, + componentWillUnmount: function() { + ContainerStore.removeListener(ContainerStore.SERVER_LOGS_EVENT, this.updateLogs); + }, + componentDidUpdate: function () { + // Scroll logs to bottom + var parent = $('.mini-logs'); + if (parent.length) { + if (parent.scrollTop() >= this._oldHeight) { + parent.stop(); + parent.scrollTop(parent[0].scrollHeight - parent.height()); + } + this._oldHeight = parent[0].scrollHeight - parent.height(); + } + }, + init: function () { + this.updateLogs(); + }, + updateLogs: function (name) { + if (name && name !== this.getParams().name) { + return; + } + this.setState({ + logs: ContainerStore.logs(this.getParams().name) + }); + }, + handleClickLogs: function () { + this.transitionTo('containerLogs', {name: this.getParams().name}); + }, + render: function () { + var logs = this.state.logs.map(function (l, i) { + return

; + }); + return ( +
+

Logs

+
+ {logs} +
View Logs
+
+
+ ); + } +}); + +module.exports = ContainerHomeLogs; diff --git a/src/ContainerHomePreview.react.js b/src/ContainerHomePreview.react.js new file mode 100644 index 00000000..dfb12d58 --- /dev/null +++ b/src/ContainerHomePreview.react.js @@ -0,0 +1,97 @@ +var _ = require('underscore'); +var $ = require('jquery'); +var React = require('react/addons'); +var exec = require('exec'); +var ContainerStore = require('./ContainerStore'); +var ContainerUtil = require('./ContainerUtil'); +var Router = require('react-router'); + +var ContainerHomePreview = React.createClass({ + mixins: [Router.State, Router.Navigation], + getInitialState: function () { + return { + ports: {}, + defaultPort: null + }; + }, + componentWillReceiveProps: function () { + this.init(); + }, + componentDidMount: function() { + this.init(); + this.timer = setInterval(this.tick, 2000); + }, + tick: function () { + if (document.getElementById('web-preview-frame')) { + var frameContent = document.getElementById('web-preview-frame').contentDocument; + var $body = $(frameContent.body); + if ($body.is(':empty')) { + document.getElementById('web-preview-frame').contentDocument.location.reload(true); + } + } + }, + componentWillUnmount: function() { + clearInterval(this.timer); + }, + init: function () { + var container = ContainerStore.container(this.getParams().name); + if (!container) { + return; + } + var ports = ContainerUtil.ports(container); + var webPorts = ['80', '8000', '8080', '3000', '5000', '2368']; + this.setState({ + ports: ports, + defaultPort: _.find(_.keys(ports), function (port) { + return webPorts.indexOf(port) !== -1; + }) + }); + }, + handleClickPreview: function () { + if (this.state.defaultPort) { + exec(['open', this.state.ports[this.state.defaultPort].url], function (err) { + if (err) { throw err; } + }); + } + }, + handleClickNotShowingCorrectly: function () { + this.transitionTo('containerSettingsPorts', {name: this.getParams().name}); + }, + render: function () { + var preview; + if (this.state.defaultPort) { + preview = ( +
+

Web Preview

+
+ +
Open in Browser
+
+
Not showing correctly?
+
+ ); + } else { + var ports = _.map(_.pairs(this.state.ports), function (pair) { + var key = pair[0]; + var val = pair[1]; + return ( +
+ {val.display} +
+ ); + }); + preview = ( +
+

IP & Ports

+
+

You can access this container from the outside using the following IP & Port(s):

+ {ports} +
+
+ ); + } + return preview; + } +}); + +module.exports = ContainerHomePreview; diff --git a/src/ContainerListItem.react.js b/src/ContainerListItem.react.js index 175def45..bf69e31c 100644 --- a/src/ContainerListItem.react.js +++ b/src/ContainerListItem.react.js @@ -66,7 +66,7 @@ var ContainerListItem = React.createClass({ } return ( - +
  • {state}
    diff --git a/src/ContainerListNewItem.react.js b/src/ContainerListNewItem.react.js index 3260aceb..0fb41752 100644 --- a/src/ContainerListNewItem.react.js +++ b/src/ContainerListNewItem.react.js @@ -3,6 +3,7 @@ var React = require('react/addons'); var Router = require('react-router'); var ContainerListNewItem = React.createClass({ + mixins: [Router.State, Router.Navigation], handleItemMouseEnter: function () { var $action = $(this.getDOMNode()).find('.action'); $action.show(); @@ -13,6 +14,7 @@ var ContainerListNewItem = React.createClass({ }, handleDelete: function () { $(this.getDOMNode()).fadeOut(); + this.transitionTo('containers'); }, render: function () { var self = this; diff --git a/src/ContainerLogs.react.js b/src/ContainerLogs.react.js new file mode 100644 index 00000000..e95ccd97 --- /dev/null +++ b/src/ContainerLogs.react.js @@ -0,0 +1,57 @@ +var $ = require('jquery'); +var React = require('react/addons'); +var ContainerStore = require('./ContainerStore'); +var Router = require('react-router'); + +var ContainerLogs = React.createClass({ + mixins: [Router.State], + getInitialState: function () { + return { + logs: [] + }; + }, + componentWillReceiveProps: function () { + this.init(); + }, + componentDidMount: function() { + this.init(); + ContainerStore.on(ContainerStore.SERVER_LOGS_EVENT, this.updateLogs); + }, + componentWillUnmount: function() { + ContainerStore.removeListener(ContainerStore.SERVER_LOGS_EVENT, this.updateLogs); + }, + componentDidUpdate: function () { + // Scroll logs to bottom + var parent = $('.details-logs'); + if (parent.length) { + if (parent.scrollTop() >= this._oldHeight) { + parent.stop(); + parent.scrollTop(parent[0].scrollHeight - parent.height()); + } + this._oldHeight = parent[0].scrollHeight - parent.height(); + } + }, + init: function () { + this.updateLogs(); + }, + updateLogs: function (name) { + if (name && name !== this.getParams().name) { + return; + } + this.setState({ + logs: ContainerStore.logs(this.getParams().name) + }); + }, + render: function () { + var logs = this.state.logs.map(function (l, i) { + return

    ; + }); + return ( +
    + {logs} +
    + ); + } +}); + +module.exports = ContainerLogs; diff --git a/src/ContainerSettings.react.js b/src/ContainerSettings.react.js new file mode 100644 index 00000000..3b0ba56e --- /dev/null +++ b/src/ContainerSettings.react.js @@ -0,0 +1,53 @@ +var _ = require('underscore'); +var React = require('react/addons'); +var Router = require('react-router'); + +var ContainerSettings = React.createClass({ + mixins: [Router.State, Router.Navigation], + componentWillReceiveProps: function () { + this.init(); + }, + componentDidMount: function() { + this.init(); + }, + init: function () { + var currentRoute = _.last(this.getRoutes()).name; + if (currentRoute === 'containerSettings') { + this.transitionTo('containerSettingsGeneral', {name: this.getParams().name}); + } + }, + render: function () { + var container = this.props.container; + if (!container) { + return (
    ); + } + return ( +
    +
    +
    +
      + +
    • + General +
    • +
      + +
    • + Ports +
    • +
      + +
    • + Volumes +
    • +
      +
    +
    + +
    +
    + ); + } +}); + +module.exports = ContainerSettings; diff --git a/src/ContainerSettingsGeneral.react.js b/src/ContainerSettingsGeneral.react.js new file mode 100644 index 00000000..c529b18b --- /dev/null +++ b/src/ContainerSettingsGeneral.react.js @@ -0,0 +1,234 @@ +var _ = require('underscore'); +var $ = require('jquery'); +var React = require('react/addons'); +var Router = require('react-router'); +var path = require('path'); +var remote = require('remote'); +var rimraf = require('rimraf'); +var fs = require('fs'); +var dialog = remote.require('dialog'); +var ContainerStore = require('./ContainerStore'); +var ContainerUtil = require('./ContainerUtil'); + +var containerNameSlugify = function (text) { + text = text.replace(/^\s+|\s+$/g, ''); // Trim + text = text.toLowerCase(); + // Remove Accents + var from = "àáäâèéëêìíïîòóöôùúüûñç·/,:;"; + var to = "aaaaeeeeiiiioooouuuunc-----"; + for (var i=0, l=from.length ; i
    ); + } + var willBeRenamedAs; + var btnSaveName = ( + Save + ); + if (this.state.slugName) { + willBeRenamedAs = ( +

    Will be renamed as: {this.state.slugName}

    + ); + btnSaveName = ( + Save + ); + } + var rename = ( +
    +

    Container Name

    +
    + + {willBeRenamedAs} +
    + {btnSaveName} +
    + ); + var self = this; + var envVars = _.map(this.state.env, function (val, key) { + return ( +
    + + + +
    + ); + }); + var pendingEnvVars = _.map(this.state.pendingEnv, function (val, key) { + return ( +
    + + + +
    + ); + }); + return ( +
    + {rename} +
    +

    Environment Variables

    +
    +
    KEY
    +
    VALUE
    +
    +
    + {envVars} + {pendingEnvVars} +
    + + + +
    +
    + Save +
    +
    +

    Delete Container

    + Delete Container +
    +
    + ); + } +}); + +module.exports = ContainerSettingsGeneral; diff --git a/src/ContainerSettingsPorts.react.js b/src/ContainerSettingsPorts.react.js new file mode 100644 index 00000000..ac0c4cc1 --- /dev/null +++ b/src/ContainerSettingsPorts.react.js @@ -0,0 +1,85 @@ +var _ = require('underscore'); +var React = require('react/addons'); +var Router = require('react-router'); +var exec = require('exec'); +var ContainerStore = require('./ContainerStore'); +var ContainerUtil = require('./ContainerUtil'); + +var ContainerSettingsPorts = React.createClass({ + mixins: [Router.State, Router.Navigation], + getInitialState: function () { + return { + ports: {}, + defaultPort: null + }; + }, + componentWillReceiveProps: function () { + this.init(); + }, + componentDidMount: function() { + this.init(); + }, + init: function () { + var container = ContainerStore.container(this.getParams().name); + if (!container) { + return; + } + var ports = ContainerUtil.ports(container); + var webPorts = ['80', '8000', '8080', '3000', '5000', '2368']; + this.setState({ + ports: ports, + defaultPort: _.find(_.keys(ports), function (port) { + return webPorts.indexOf(port) !== -1; + }) + }); + }, + handleViewLink: function (url) { + exec(['open', url], function (err) { + if (err) { throw err; } + }); + }, + handleChangeDefaultPort: function (port, e) { + if (e.target.checked) { + this.setState({ + defaultPort: null + }); + } else { + this.setState({ + defaultPort: port + }); + } + }, + render: function () { + if (!this.props.container) { + return (
    ); + } + var self = this; + var ports = _.map(_.pairs(self.state.ports), function (pair) { + var key = pair[0]; + var val = pair[1]; + return ( +
    + {key} + {val.display} + +
    + ); + }); + return ( +
    +
    +

    Configure Ports

    +
    +
    +
    DOCKER PORT
    +
    MAC PORT
    +
    + {ports} +
    +
    +
    + ); + } +}); + +module.exports = ContainerSettingsPorts; diff --git a/src/ContainerSettingsVolumes.react.js b/src/ContainerSettingsVolumes.react.js new file mode 100644 index 00000000..a17d08cc --- /dev/null +++ b/src/ContainerSettingsVolumes.react.js @@ -0,0 +1,82 @@ +var _ = require('underscore'); +var React = require('react/addons'); +var Router = require('react-router'); +var remote = require('remote'); +var exec = require('exec'); +var dialog = remote.require('dialog'); +var ContainerStore = require('./ContainerStore'); + +var ContainerSettingsVolumes = React.createClass({ + mixins: [Router.State, Router.Navigation], + handleChooseVolumeClick: function (dockerVol) { + var self = this; + dialog.showOpenDialog({properties: ['openDirectory', 'createDirectory']}, function (filenames) { + if (!filenames) { + return; + } + var directory = filenames[0]; + if (directory) { + var volumes = _.clone(self.props.container.Volumes); + volumes[dockerVol] = directory; + var binds = _.pairs(volumes).map(function (pair) { + return pair[1] + ':' + pair[0]; + }); + ContainerStore.updateContainer(self.props.container.Name, { + Binds: binds + }, function (err) { + if (err) { console.log(err); } + }); + } + }); + }, + handleOpenVolumeClick: function (path) { + exec(['open', path], function (err) { + if (err) { throw err; } + }); + }, + render: function () { + if (!this.props.container) { + return (
    ); + } + var self = this; + var volumes = _.map(self.props.container.Volumes, function (val, key) { + if (!val || val.indexOf(process.env.HOME) === -1) { + val = ( + + No Folder + Change + + ); + } else { + val = ( + + {val.replace(process.env.HOME, '~')} + Change + + ); + } + return ( +
    + {key} + {val} +
    + ); + }); + return ( +
    +
    +

    Configure Volumes

    +
    +
    +
    DOCKER FOLDER
    +
    MAC FOLDER
    +
    + {volumes} +
    +
    +
    + ); + } +}); + +module.exports = ContainerSettingsVolumes; diff --git a/src/ContainerUtil.js b/src/ContainerUtil.js index 8ff36e66..044ba165 100644 --- a/src/ContainerUtil.js +++ b/src/ContainerUtil.js @@ -22,7 +22,7 @@ var ContainerUtil = { if (value && value.length) { var port = value[0].HostPort; localUrl = 'http://' + ip + ':' + port; - localUrlDisplay = ip + ': ' + port; + localUrlDisplay = ip + ':' + port; } res[dockerPort] = { url: localUrl, diff --git a/src/Containers.react.js b/src/Containers.react.js index 0446677d..48945664 100644 --- a/src/Containers.react.js +++ b/src/Containers.react.js @@ -20,7 +20,7 @@ var Containers = React.createClass({ ContainerStore.on(ContainerStore.CLIENT_CONTAINER_EVENT, this.updateFromClient); if (this.state.sorted.length) { - this.transitionTo('container', {name: this.state.sorted[0].Name}); + this.transitionTo('containerHome', {name: this.state.sorted[0].Name}); } }, componentDidUnmount: function () { @@ -34,7 +34,7 @@ var Containers = React.createClass({ }); if (status === 'destroy') { if (this.state.sorted.length) { - this.transitionTo('container', {name: this.state.sorted[0].Name}); + this.transitionTo('containerHome', {name: this.state.sorted[0].Name}); } else { this.transitionTo('containers'); } @@ -46,7 +46,7 @@ var Containers = React.createClass({ sorted: ContainerStore.sorted() }); if (status === 'create') { - this.transitionTo('container', {name: name}); + this.transitionTo('containerHome', {name: name}); } }, handleScroll: function (e) { diff --git a/src/NewContainer.react.js b/src/NewContainer.react.js index fa325242..02b3a17a 100644 --- a/src/NewContainer.react.js +++ b/src/NewContainer.react.js @@ -11,7 +11,7 @@ var NewContainer = React.createClass({ getInitialState: function () { return { query: '', - results: ContainerStore.recommended(), + results: [], loading: false, tags: {}, active: null, @@ -24,6 +24,7 @@ var NewContainer = React.createClass({ }); this.refs.searchInput.getDOMNode().focus(); ContainerStore.on(ContainerStore.CLIENT_RECOMMENDED_EVENT, this.update); + this.update(); }, update: function () { if (!this.state.query.length) { @@ -105,8 +106,10 @@ var NewContainer = React.createClass({ render: function () { var self = this; var title = this.state.query ? 'Results' : 'Recommended'; - var data = this.state.results.slice(0, 6); - + var data = []; + if (this.state.results) { + data = this.state.results.slice(0, 6); + } var results; if (data.length) { var items = data.map(function (r) { @@ -173,11 +176,22 @@ var NewContainer = React.createClass({
  • ); } else { - results = ( -
    - -
    - ); + if (this.state.results.length === 0 && this.state.query === '') { + results = ( +
    +
    +

    Loading Images

    + +
    +
    + ); + } else { + results = ( +
    +

    Cannot find a matching image.

    +
    + ); + } } var loadingClasses = React.addons.classSet({ hidden: !this.state.loading, @@ -198,7 +212,7 @@ var NewContainer = React.createClass({
    - +
    diff --git a/src/Radial.react.js b/src/Radial.react.js index 011fb1ff..7d1ee5bf 100644 --- a/src/Radial.react.js +++ b/src/Radial.react.js @@ -15,7 +15,8 @@ var Radial = React.createClass({ 'radial-spinner': this.props.spin, 'radial-negative': this.props.error, 'radial-thick': this.props.thick || false, - 'radial-gray': this.props.gray || false + 'radial-gray': this.props.gray || false, + 'radial-transparent': this.props.transparent || false }); return (
    diff --git a/src/Routes.js b/src/Routes.js index 847fb8d1..16d82a5c 100644 --- a/src/Routes.js +++ b/src/Routes.js @@ -2,6 +2,12 @@ var React = require('react/addons'); var Setup = require('./Setup.react'); var Containers = require('./Containers.react'); var ContainerDetails = require('./ContainerDetails.react'); +var ContainerHome = require('./ContainerHome.react'); +var ContainerLogs = require('./ContainerLogs.react'); +var ContainerSettings = require('./ContainerSettings.react'); +var ContainerSettingsGeneral = require('./ContainerSettingsGeneral.react'); +var ContainerSettingsPorts = require('./ContainerSettingsPorts.react'); +var ContainerSettingsVolumes = require('./ContainerSettingsVolumes.react'); var Preferences = require('./Preferences.react'); var NewContainer = require('./NewContainer.react'); var Router = require('react-router'); @@ -21,7 +27,15 @@ var App = React.createClass({ var routes = ( - + + + + + + + + + diff --git a/styles/containers.less b/styles/containers.less index 0c310ce5..750a29ae 100644 --- a/styles/containers.less +++ b/styles/containers.less @@ -28,7 +28,7 @@ .image-item { display: flex; width: 320px; - height: 170px; + height: 166px; border-radius: 4px; border: 1px solid @gray-lightest; background-color: white; @@ -50,6 +50,10 @@ font-size: 18px; color: @gray-darkest; margin-bottom: 5px; + width: 190px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; img { margin-right: 7px; position: relative; @@ -59,7 +63,7 @@ .description { font-size: 12px; color: @gray-normal; - height: 70px; + height: 65px; text-overflow: ellipsis; overflow: hidden; -webkit-box-orient: vertical; @@ -136,6 +140,21 @@ flex: 1 auto; display: flex; align-items: center; + .loader { + margin: 0 auto; + margin-top: -20%; + text-align: center; + width: 300px; + h2 { + margin-bottom: 20px; + } + } + h1 { + color: @gray-lightest; + font-size: 24px; + margin: 0 auto; + margin-top: -20%; + } } } .new-container-header { @@ -253,7 +272,7 @@ color: @brand-action; transition: all 0.25s; &:hover { - color: darken(@brand-action, 10%); + color: darken(@brand-action, 15%); } } } @@ -306,6 +325,8 @@ .btn-delete { font-size: 24px; color: white; + position: relative; + z-index: 9999; } .state-new { .at2x('container-white.png', 20px, 20px); @@ -353,6 +374,7 @@ margin-left: 16px; .name { text-overflow: ellipsis; + max-width: 140px; white-space: nowrap; overflow: hidden; font-size: 14px; @@ -364,6 +386,7 @@ font-size: 12px; font-weight: 400; text-overflow: ellipsis; + max-width: 140px; white-space: nowrap; overflow: hidden; } @@ -379,6 +402,8 @@ .btn-delete { font-size: 24px; color: @gray-lighter; + position: relative; + z-index: 9999; } } @@ -521,6 +546,9 @@ margin-top: -12px; .action { display: inline-block; + &.disabled { + opacity: 0.3; + } .action-icon { color: @gray-normal; font-size: 30px; @@ -564,6 +592,9 @@ color: white; background-image: linear-gradient(-180deg, #24B8EB 4%, #24A3EB 100%); } + &.disabled { + opacity: 0.5; + } } } } @@ -657,129 +688,155 @@ .left { width: 60%; flex-direction: column; - .web-preview { - margin-right: 30px; - .subtext { - text-align: right; - color: @gray-lightest; - margin-top: 2px; - } - .widget { - background-color: white; - width: 100%; - height: 100%; - border-radius: 4px; - border: 1px solid @gray-lightest; - position: relative; - iframe { - border: 0; - border-radius: 4px; - /*width: 100%; - height: 100%;*/ - position: relative; - top: -50%; - left: -50%; - width: 200%; - height: 200%; - transform: scale(0.5); - } - .iframe-overlay { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 100; - color: transparent; - transition: all 0.25s; - .icon { - margin-top: 40%; - display: block; - font-size: 60px; - text-align: center; - } - .text { - font-size: 20px; - text-align: center; - } - &:hover { - color: white; - background-color: @gray-darkest; - opacity: 0.75; - } - } - } - } + margin-right: 30px; } .right { width: 40%; flex-direction: column; - .mini-logs { - margin-bottom: 50px; - .widget { - position: relative; - border-radius: 4px; - border: 1px solid @gray-lightest; - background-color: @gray-darkest; - color: @gray-lightest; - height: 100%; + } + .web-preview { + margin-bottom: 50px; + .subtext { + text-align: right; + color: @gray-lightest; + margin-top: 2px; + transition: all 0.25s; + &:hover { + color: darken(@gray-lightest, 10%); + } + } + .widget { + background-color: white; + width: 100%; + height: 100%; + border-radius: 4px; + border: 1px solid @gray-lightest; + position: relative; + p { + font-size: 13px; + color: @gray-normal; padding: 10px; - overflow: hidden; + padding-bottom: 0px; + } + .ip-port { + padding: 20px; + padding-top: 5px; + color: @gray-darkest; font-family: Menlo; - font-size: 8px; - white-space: pre-wrap; - p { - margin-bottom: 0px; + -webkit-user-select: text; + } + iframe { + border: 0; + border-radius: 4px; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + transform: scale(0.5); + } + .iframe-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 100; + color: transparent; + transition: all 0.25s; + .icon { + margin-top: 40%; + display: block; + font-size: 60px; + text-align: center; } - .mini-logs-overlay { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 100; - color: transparent; - transition: all 0.25s; - .icon { - margin-top: 25%; - display: block; - font-size: 60px; - text-align: center; - } - .text { - font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; - font-size: 20px; - text-align: center; - } - &:hover { - color: white; - background-color: @gray-darkest; - opacity: 0.75; - } + .text { + font-size: 20px; + text-align: center; + } + &:hover { + color: white; + background-color: @gray-darkest; + opacity: 0.75; } } } - .folders { - .subtext { - text-align: right; - color: @gray-lightest; - margin-top: 2px; + } + .mini-logs { + margin-bottom: 50px; + .widget { + position: relative; + border-radius: 4px; + border: 1px solid @gray-lightest; + background-color: @gray-darkest; + color: @gray-lightest; + height: 100%; + padding: 10px; + overflow: hidden; + font-family: Menlo; + font-size: 7px; + white-space: pre; + p { + margin-bottom: 0px; } - .widget { - padding: 20px 10px; - background-color: white; - border-radius: 4px; - border: 1px solid @gray-lightest; - display: flex; - .folder { - width: 100px; - img { - display: block; - margin: 0 auto; - } - .text { - text-align: center; - } + .mini-logs-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 100; + color: transparent; + transition: all 0.25s; + .icon { + margin-top: 25%; + display: block; + font-size: 60px; + text-align: center; + } + .text { + font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; + font-size: 20px; + text-align: center; + } + &:hover { + color: white; + background-color: @gray-darkest; + opacity: 0.75; + } + } + } + } + .folders { + .subtext { + text-align: right; + color: @gray-lightest; + margin-top: 2px; + transition: all 0.25s; + &:hover { + color: darken(@gray-lightest, 10%); + } + } + .widget { + padding: 10px 5px; + background-color: white; + border-radius: 4px; + border: 1px solid @gray-lightest; + display: flex; + .folder { + width: 110px; + padding: 5px; + &:hover { + background-color: #F9F9F9; + border-radius: 10px; + } + img { + display: block; + margin: 0 auto; + } + .text { + margin-top: 4px; + text-align: center; } } } @@ -799,108 +856,62 @@ } } .settings { - padding: 18px 38px; - .settings-section { - margin-bottom: 40px; - } - } - .ports { - padding: 18px 38px; - } - .volumes { - padding: 18px 38px; - } - - .table { - margin-bottom: 0; - .icon-arrow-right { - color: #aaa; - margin: 2px 9px 0; - flex: 0 auto; - min-width: 13px; - } - .btn { - min-width: 22px; - margin-left: 10px; - } - .table-labels { - margin-top: 20px; - flex: 1 auto; - display: flex; - font-size: 12px; - color: @gray-lightest; - .label-left { - flex: 0 auto; - min-width: 80px; - margin-right: 30px; - text-align: right; - } - .label-right { - flex: 1 auto; - display: inline-block; - width: 40%; - } - } - .table-values { - flex: 1 auto; - display: flex; - flex-direction: row; - margin: 8px 0; - .value-left { - text-align: right; - min-width: 80px; - flex: 0 auto; - } - .value-right { - flex: 1 auto; - -webkit-user-select: text; - width: 40%; - } - } - .table-new { - margin-top: 10px; - flex: 1 auto; - display: flex; - input { + display: flex; + flex: 1 auto; + flex-direction: row; + .settings-menu { + min-width: 160px; + ul { + position: fixed; + margin: 0; padding: 0; - font-weight: 400; - } - input.new-left { - flex: 0 auto; - text-align: right; - min-width: 80px; - max-width: 80px; - } - .new-right-wrapper { - position: relative; + padding-top: 14px; + display: flex; - flex: 1 auto; - .new-right-placeholder { - position: absolute; - top: 3px; - left: 0; - font-weight: 400; + flex-direction: column; + + a { + min-width: 160px; + margin-left: 12px; + color: @gray-normal; + flex-shrink: 0; + cursor: default; + outline: none; + margin-bottom: 10px; + &.active { + li { + color: white; + border-radius: 40px; + background-image: linear-gradient(-180deg, #24B8EB 4%, #24A3EB 100%); + } + } + &:hover { + text-decoration: none; + li { + cursor: default; + border-radius: 40px; + background-color: #F9F9F9; + } + } + &:focus { + text-decoration: none; + } } - input.new-right { - flex: 1 auto; - height: 24px; - position :relative; - padding-left: 107px; + li { + vertical-align: middle; + padding: 5px 12px; + display: flex; + flex-direction: row; } } } - - &.volumes { - .label-left { - min-width: 120px; - } - .value-left { - min-width: 120px; - } - .icon { - color: #aaa; - margin: 2px 9px 0; + .settings-panel { + padding-left: 40px; + width: 100%; + overflow-x: hidden; + .settings-section { + margin-bottom: 40px; } } } @@ -909,7 +920,16 @@ .container-name { margin-bottom: 20px; input { - width: 20%; + width: 40%; + } + p { + font-weight: 300; + margin-top: 5px; + color: @gray-lighter; + font-size: 12px; + strong { + font-weight: 500; + } } } @@ -923,11 +943,11 @@ .label-key { display: inline-block; margin-right: 30px; - width: 20%; + width: 30%; } .label-val { display: inline-block; - width: 40%; + width: 50%; } } .env-vars { @@ -938,10 +958,109 @@ input { margin-right: 30px; &.key { - width: 20%; + width: 30%; } &.val { - width: 40%; + width: 50%; + } + } + } + + .table { + margin-bottom: 0; + .icon-arrow-right { + color: #BBB; + font-size: 20px; + margin: 0px 10px; + flex: 0 auto; + min-width: 13px; + } + &.ports { + .table-labels { + margin-top: 20px; + flex: 1 auto; + display: flex; + font-size: 12px; + color: @gray-lightest; + .label-left { + flex: 0 auto; + min-width: 85px; + margin-right: 30px; + text-align: right; + } + .label-right { + flex: 1 auto; + display: inline-block; + margin-left: 10px; + width: 40%; + } + } + .table-values { + flex: 1 auto; + display: flex; + flex-direction: row; + margin: 8px 0; + .value-left { + text-align: right; + min-width: 85px; + flex: 0 auto; + padding: 0px; + } + .value-right { + flex: 1 auto; + -webkit-user-select: text; + max-width: 170px; + padding: 0px; + } + label { + margin-left: 8px; + margin-top: 1px; + font-weight: 400; + font-size: 13px; + } + input[type="checked"] { + + } + } + } + &.volumes { + .table-labels { + margin-top: 20px; + flex: 1 auto; + display: flex; + font-size: 12px; + color: @gray-lightest; + .label-left { + flex: 0 auto; + margin-right: 30px; + width: 30%; + } + .label-right { + flex: 1 auto; + display: inline-block; + margin-left: 10px; + width: 60%; + } + } + .table-values { + flex: 1 auto; + display: flex; + flex-direction: row; + margin: 8px 0; + .value-left { + width: 30%; + flex: 0 auto; + padding: 0px; + } + .value-right { + flex: 1 auto; + -webkit-user-select: text; + width: 60%; + padding: 0px; + } + .btn { + margin-left: 10px; + } } } } diff --git a/styles/header.less b/styles/header.less index 5eaca39d..63c7c4a4 100644 --- a/styles/header.less +++ b/styles/header.less @@ -4,7 +4,7 @@ position: absolute; min-width: 100%; flex: 0; - min-height: 30px; + min-height: 40px; -webkit-app-region: drag; -webkit-user-select: none; // border-bottom: 1px solid #efefef; diff --git a/styles/radial.less b/styles/radial.less index 91d2ffb1..b814eb5b 100644 --- a/styles/radial.less +++ b/styles/radial.less @@ -90,24 +90,11 @@ .inset { width: @inset-size; height: @inset-size; - position: absolute; margin-left: (@circle-size - @inset-size) / 2.0; margin-top: (@circle-size - @inset-size) / 2.0; - - background-color: @inset-color; - border-radius: 100%; .percentage { - width: @percentage-text-width; - position: absolute; top: (@inset-size - @percentage-font-size) / 2.0; left: (@inset-size - @percentage-text-width) / 2.0; - - line-height: 1; - text-align: center; - - color: @brand-primary; - font-weight: 500; - font-size: @percentage-font-size; } } } @@ -116,6 +103,14 @@ background: #EEE; } + &.radial-transparent { + @inset-color: #F9F9F9; + background: #F9F9F9; + .inset { + background-color: @inset-color; + } + } + @i: 0; @increment: 180deg / 100; .loop (@i) when (@i <= 100) { diff --git a/styles/theme.less b/styles/theme.less index 2a404974..c31c87d1 100644 --- a/styles/theme.less +++ b/styles/theme.less @@ -68,8 +68,8 @@ input[type="text"] { &:hover, &:focus { - border-color: darken(@btn-color, 10%); - color: darken(@btn-color, 10%); + border-color: darken(@btn-color, 15%); + color: darken(@btn-color, 15%); cursor: default; box-shadow: none; background: none; @@ -77,8 +77,8 @@ input[type="text"] { &:active { background-color: lighten(@btn-color, 45%); - border-color: darken(@btn-color, 10%); - color: darken(@btn-color, 10%); + border-color: darken(@btn-color, 15%); + color: darken(@btn-color, 15%); box-shadow: none; } @@ -131,11 +131,13 @@ input[type="text"] { box-shadow: none; font-weight: 400; text-shadow: none; - padding: 4px 14px 4px 14px; - height: 28px; + padding: 5px 14px 5px 14px; + height: 30px; cursor: default; &.small { + font-size: 11px; + padding: 3px 8px 3px 8px; height: 22px; .icon { font-size: 10px; @@ -179,7 +181,7 @@ input[type="text"] { padding: 6px 7px 6px 7px; &.small { width: 22px; - padding: 2px 5px 3px 5px; + padding: 4px 5px 4px 5px; } } }