├── .dockerignore ├── .gitignore ├── AUTHORS.rst ├── CONTRIBUTING.rst ├── Changelog.md ├── Dockerfile ├── HISTORY.rst ├── LICENSE ├── README.md ├── app ├── assets │ └── css │ │ ├── style-responsive.css │ │ ├── style.css │ │ ├── table-responsive.css │ │ └── to-do.css ├── components │ ├── Main.jsx │ ├── aside.jsx │ ├── aside │ │ ├── menu_link.jsx │ │ └── user.jsx │ ├── dashboard.jsx │ ├── dashboard │ │ ├── job_counter.jsx │ │ ├── job_last_10.jsx │ │ ├── minion_counter.jsx │ │ └── minion_keys.jsx │ ├── errors.jsx │ ├── job_follow.jsx │ ├── job_history.jsx │ ├── job_result.jsx │ ├── job_result │ │ ├── job_empty_result.jsx │ │ ├── job_raw_result.jsx │ │ ├── job_state_result.jsx │ │ └── minion_list.jsx │ ├── job_run.jsx │ ├── job_templates.jsx │ ├── minion_list.jsx │ ├── navbar.jsx │ ├── notification.jsx │ └── shared │ │ ├── hiding.js │ │ └── templates.js ├── dispatcher.js ├── errors │ ├── actions.js │ └── hec.js ├── hec │ ├── base.js │ ├── session.js │ └── settings.js ├── history.js ├── index.jsx ├── jobs │ ├── actions.js │ ├── hec.js │ ├── services.js │ ├── store.js │ └── utils.js ├── login │ ├── AuthService.js │ ├── LoginActions.js │ ├── LoginComponent.js │ ├── hec.js │ └── middleware.js ├── minions │ ├── actions.js │ ├── hec.js │ └── services.js ├── path_utils.js ├── routes.jsx ├── services │ ├── auto_updater.js │ ├── base_service.js │ └── real-time.js ├── store.js └── test.js ├── bower.json ├── devServer.js ├── docker-compose.yml ├── docs ├── README.md ├── advanced_configuration.md ├── contribute.md ├── debug.md ├── installation.md ├── installation │ ├── apache-across-internet-cors.md │ ├── apache-rest-cherrypy-ssl.md │ ├── apache-rest-cherrypy.md │ ├── apache-server.md │ ├── index.md │ ├── nginx-across-internet-cors.md │ ├── nginx-rest-cherrypy-ssl.md │ ├── nginx-rest-cherrypy.md │ ├── nginx-server.md │ ├── salt-api-cherrypy-cors.md │ ├── salt-api-cherrypy-embedded.md │ └── salt-api-tornado-cors.md ├── limitations.md ├── requirements.md ├── saltpad-docker.md ├── security.md ├── update.md └── vision.md ├── index-dev.html ├── index-prod.html ├── package.json ├── salt_master_docker ├── Dockerfile ├── Dockerfile_stable ├── config │ ├── master │ ├── minion │ └── run_salt_master.sh └── roots │ └── salt │ ├── saltapi.sls │ ├── saltpad.site │ ├── saltpad.sls │ ├── sample.sls │ └── top.sls ├── screenshots ├── README.rst └── highstate_result.png ├── settings.json.sample ├── vagrant ├── README.rst ├── Vagrantfile └── salt │ ├── key │ ├── minion.pem │ └── minion.pub │ ├── master │ ├── minion │ └── roots │ └── salt │ ├── saltapi.sls │ ├── saltpad.site │ ├── saltpad.sls │ ├── sample.sls │ ├── settings.json │ └── top.sls ├── webpack.config.dev.js ├── webpack.config.playground.js └── webpack.config.prod.js /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | build 3 | bower_components 4 | node_modules 5 | vagrant 6 | salt_master_docker 7 | screenshots 8 | docs 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac os X 2 | *.DS_Store 3 | 4 | *.py[cod] 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Packages 10 | *.egg 11 | *.egg-info 12 | dist 13 | build 14 | eggs 15 | parts 16 | bin 17 | var 18 | sdist 19 | develop-eggs 20 | .installed.cfg 21 | lib 22 | lib64 23 | __pycache__ 24 | 25 | # Installer logs 26 | pip-log.txt 27 | 28 | # Unit test / coverage reports 29 | .coverage 30 | .tox 31 | nosetests.xml 32 | 33 | # Translations 34 | *.mo 35 | 36 | # Mr Developer 37 | .mr.developer.cfg 38 | .project 39 | .pydevproject 40 | 41 | # PyCharm 42 | .idea 43 | 44 | # we shoudnt need venv in git 45 | venv/ 46 | .tox 47 | 48 | # JS 49 | node_modules/ 50 | bower_components/ 51 | dist/ 52 | 53 | settings.json 54 | 55 | # Dist 56 | dist.* 57 | 58 | # Vagrant 59 | vagrant/.vagrant/ 60 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Credits 3 | ======= 4 | 5 | Development Lead 6 | ---------------- 7 | 8 | * Boris FELD 9 | 10 | Contributors 11 | ------------ 12 | 13 | None yet. Why not be the first? 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Contributing 3 | ============ 4 | 5 | Contributions are welcome, and they are greatly appreciated! Every 6 | little bit helps, and credit will always be given. 7 | 8 | You can contribute in many ways: 9 | 10 | Types of Contributions 11 | ---------------------- 12 | 13 | Report Bugs 14 | ~~~~~~~~~~~ 15 | 16 | Report bugs at https://github.com/tinyclues/saltpad/issues. 17 | 18 | If you are reporting a bug, please include: 19 | 20 | * Your operating system name and version. 21 | * Any details about your local setup that might be helpful in troubleshooting. 22 | * Detailed steps to reproduce the bug. 23 | 24 | Fix Bugs 25 | ~~~~~~~~ 26 | 27 | Look through the GitHub issues for bugs. Anything tagged with "bug" 28 | is open to whoever wants to implement it. 29 | 30 | Implement Features 31 | ~~~~~~~~~~~~~~~~~~ 32 | 33 | Look through the GitHub issues for features. Anything tagged with "feature" 34 | is open to whoever wants to implement it. 35 | 36 | Write Documentation 37 | ~~~~~~~~~~~~~~~~~~~ 38 | 39 | SaltPad could always use more documentation, whether as part of the 40 | official SaltPad docs, in docstrings, or even on the web in blog posts, 41 | articles, and such. 42 | 43 | Submit Feedback 44 | ~~~~~~~~~~~~~~~ 45 | 46 | The best way to send feedback is to file an issue at https://github.com/tinyclues/saltpad/issues. 47 | 48 | If you are proposing a feature: 49 | 50 | * Explain in detail how it would work. 51 | * Keep the scope as narrow as possible, to make it easier to implement. 52 | * Remember that this is a volunteer-driven project, and that contributions 53 | are welcome :) 54 | 55 | Get Started! 56 | ------------ 57 | 58 | Ready to contribute? Here's how to set up `saltpad` for local development. 59 | 60 | 1. Fork the `saltpad` repo on GitHub. 61 | 2. Clone your fork locally:: 62 | 63 | $ git clone git@github.com:your_name_here/saltpad.git 64 | 65 | 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: 66 | 67 | $ mkvirtualenv saltpad 68 | $ cd saltpad/ 69 | $ python setup.py develop 70 | 71 | 4. Create a branch for local development:: 72 | 73 | $ git checkout -b name-of-your-bugfix-or-feature 74 | 75 | Now you can make your changes locally. 76 | 77 | 5. When you're done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox:: 78 | 79 | $ flake8 saltpad tests 80 | $ python setup.py test 81 | $ tox 82 | 83 | To get flake8 and tox, just pip install them into your virtualenv. 84 | 85 | 6. Commit your changes and push your branch to GitHub:: 86 | 87 | $ git add . 88 | $ git commit -m "Your detailed description of your changes." 89 | $ git push origin name-of-your-bugfix-or-feature 90 | 91 | 7. Submit a pull request through the GitHub website. 92 | 93 | Pull Request Guidelines 94 | ----------------------- 95 | 96 | Before you submit a pull request, check that it meets these guidelines: 97 | 98 | 1. The pull request should include tests. 99 | 2. If the pull request adds functionality, the docs should be updated. Put 100 | your new functionality into a function with a docstring, and add the 101 | feature to the list in README.rst. 102 | 3. The pull request should work for Python 2.6, 2.7, and 3.3, and for PyPy. Check 103 | https://travis-ci.org/tinyclues/saltpad/pull_requests 104 | and make sure that the tests pass for all supported Python versions. 105 | 106 | Tips 107 | ---- 108 | 109 | To run a subset of tests:: 110 | 111 | $ python -m unittest tests.test_saltpad 112 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [0.3.1](https://github.com/tinyclues/saltpad/tree/0.3.1) (2016-04-19) 4 | 5 | No-feature release for v0.3, fixing the archive behavior not creating the 'static' directory, users please update to this release! 6 | 7 | - Fix the dist.zip archive to correctly create the "static" directory. Sorry for the inconvenience. 8 | - The "settings.json" file should be located in the "static" directory and not in a "config" directory, documentation has been fixed. 9 | 10 | ## [0.3](https://github.com/tinyclues/saltpad/tree/0.3) (2016-03-03) 11 | 12 | This release has been updated and a new dist.zip has been uploaded, it may have broke any script which uses the old md5 with the same archive url, sorry for the inconvenience. I'll release a 0.3.1 right now to fix this and delete downloads for this release. 13 | 14 | This release focus on most-requests feature, the support of rest_cherrypy stable! It landed in the v0.3 version in beta for the moment. Here is the detailed changelog: 15 | 16 | - Make the external_auth configurable in the settings. It is optional and the default value is 'pam'. 17 | - Better support for highstate with minions in error. 18 | - Fix some style issues for Firefox. 19 | - Add beta support for rest-cherrypy with embedded single-app page deployment. 20 | - Add support for prefix configuration required by the support for embedded single-app page deployment but could be usefull in other cases. 21 | - Update the docker environment and add a docker-compose with salt-api linked container. 22 | 23 | The Readme has been updated to include all required informations for testing saltpad with the new docker environment and against a rest_cherrypy stable version on your production. 24 | 25 | The settings.json file should now be placed in static/ directory, apart from that upgrade doesn't requires particular steps, download the new dist.zip, check it md5sum or sha1sum and it should works! 26 | 27 | ## [0.2](https://github.com/tinyclues/saltpad/tree/0.2) (2016-01-22) 28 | 29 | This release focus on error reporting and fixing the few issues that were found by early testers: 30 | 31 | - Error page when saltpad couldn't load the settings file. 32 | - Error message on token expiration. 33 | - Error message on invalid credentials. 34 | - Fix the "Mixed Content" due to google fonts css was loaded over http. 35 | - Improve the performance by avoiding multiple rendering. 36 | - Numerous contribution on the README. 37 | 38 | Upgrade doesn't requires particual steps, download the new dist.zip, check it md5sum or sha1sum and it should works! 39 | 40 | Big thank you to the contributors who helped both alpha-test this release and made big contributions! 41 | 42 | ## [0.1](https://github.com/tinyclues/saltpad/tree/0.1) (2016-01-06) 43 | 44 | Saltpad has been entirely rewritten as a single app page using React. This leads to several enhancements: 45 | 46 | - Better responsiveness. 47 | - More interactive. 48 | - Helps solves the job launching UX. 49 | - Easier to deploy. 50 | 51 | This rewrite and architecture change now require that the salt-api is contactable from the browser directly. If it's a concern for you and don't want to use the new version because of it, please open an issue. 52 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node 2 | MAINTAINER Boris Feld 3 | 4 | # Prepare app directory 5 | RUN mkdir -p /usr/src/app 6 | WORKDIR /usr/src/app 7 | 8 | 9 | COPY package.json . 10 | RUN npm install 11 | COPY bower.json . 12 | RUN ./node_modules/bower/bin/bower install --allow-root 13 | 14 | COPY . /usr/src/app 15 | 16 | VOLUME /usr/src/app 17 | 18 | EXPOSE 3333 19 | CMD npm start 20 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | .. :changelog: 2 | 3 | History 4 | ------- 5 | 6 | 0.0.1 (2014-01-11) 7 | ++++++++++++++++++ 8 | 9 | * First release on PyPI. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | SaltPad - GUI and CLI tool to manage saltstack deployments + orchestration 2 | 3 | Copyright (c) 2014, Boris FELD 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![No Maintenance Intended](http://unmaintained.tech/badge.svg)](http://unmaintained.tech/) 2 | 3 | # What is SaltPad? 4 | 5 | SaltPad is a GUI tool to manage SaltStack deployments + orchestration. It's still very young and it should be considered as Alpha. 6 | 7 | ![Image of saltpad](screenshots/highstate_result.png) 8 | 9 | A walkthrough using screenshots is available in the screenshots directory (not up-to-date). 10 | 11 | **This version of saltpad is a full-rewrite as a single app page. The old version in python is still available in the saltpad_v1 branch (https://github.com/tinyclues/saltpad/tree/saltpad_v1). If you cannot use this version, please open an issue to ease migration and see the Changelog (https://github.com/tinyclues/saltpad/blob/master/Changelog.md).** 12 | 13 | ## Features 14 | 15 | * Get overview of all your minion. 16 | * Get details about each minions, its Salt version. 17 | * Easy launch of state.highstate jobs with or without dry-run mode. 18 | * Manage minion keys. 19 | * Launch jobs. 20 | * Access jobs details easily. 21 | * Save job configuration as templates and launch them with one click on a button. 22 | * Quick debug minion, get all useful information in one place. 23 | 24 | ## Installation and documentation 25 | 26 | You can follow installation instructions and more documentation [in the docs directory](docs/README.md). 27 | -------------------------------------------------------------------------------- /app/assets/css/style-responsive.css: -------------------------------------------------------------------------------- 1 | @media (min-width: 980px) { 2 | /*-----*/ 3 | .custom-bar-chart { 4 | margin-bottom: 40px; 5 | } 6 | 7 | } 8 | 9 | @media (min-width: 768px) and (max-width: 979px) { 10 | 11 | /*-----*/ 12 | .custom-bar-chart { 13 | margin-bottom: 40px; 14 | } 15 | 16 | /*chat room*/ 17 | 18 | 19 | } 20 | 21 | @media (max-width: 768px) { 22 | 23 | .header { 24 | position: absolute; 25 | } 26 | 27 | /*sidebar*/ 28 | 29 | #sidebar { 30 | height: auto; 31 | overflow: hidden; 32 | position: absolute; 33 | width: 100%; 34 | z-index: 1001; 35 | } 36 | 37 | 38 | /* body container */ 39 | #main-content { 40 | margin: 0px!important; 41 | position: none !important; 42 | } 43 | 44 | #sidebar > ul > li > a > span { 45 | line-height: 35px; 46 | } 47 | 48 | #sidebar > ul > li { 49 | margin: 0 10px 5px 10px; 50 | } 51 | #sidebar > ul > li > a { 52 | height:35px; 53 | line-height:35px; 54 | padding: 0 10px; 55 | text-align: left; 56 | } 57 | #sidebar > ul > li > a i{ 58 | /*display: none !important;*/ 59 | } 60 | 61 | #sidebar ul > li > a .arrow, #sidebar > ul > li > a .arrow.open { 62 | margin-right: 10px; 63 | margin-top: 15px; 64 | } 65 | 66 | #sidebar ul > li.active > a .arrow, #sidebar ul > li > a:hover .arrow, #sidebar ul > li > a:focus .arrow, 67 | #sidebar > ul > li.active > a .arrow.open, #sidebar > ul > li > a:hover .arrow.open, #sidebar > ul > li > a:focus .arrow.open{ 68 | margin-top: 15px; 69 | } 70 | 71 | #sidebar > ul > li > a, #sidebar > ul > li > ul.sub > li { 72 | width: 100%; 73 | } 74 | #sidebar > ul > li > ul.sub > li > a { 75 | background: transparent !important ; 76 | } 77 | #sidebar > ul > li > ul.sub > li > a:hover { 78 | 79 | } 80 | 81 | 82 | /* sidebar */ 83 | #sidebar { 84 | margin: 0px !important; 85 | } 86 | 87 | /* sidebar collabler */ 88 | #sidebar .btn-navbar.collapsed .arrow { 89 | display: none; 90 | } 91 | 92 | #sidebar .btn-navbar .arrow { 93 | position: absolute; 94 | right: 35px; 95 | width: 0; 96 | height: 0; 97 | top:48px; 98 | border-bottom: 15px solid #282e36; 99 | border-left: 15px solid transparent; 100 | border-right: 15px solid transparent; 101 | } 102 | 103 | 104 | /*---------*/ 105 | 106 | .modal-footer .btn { 107 | margin-bottom: 0px !important; 108 | } 109 | 110 | .btn { 111 | margin-bottom: 5px; 112 | } 113 | 114 | 115 | /* full calendar fix */ 116 | .fc-header-right { 117 | left:25px; 118 | position: absolute; 119 | } 120 | 121 | .fc-header-left .fc-button { 122 | margin: 0px !important; 123 | top: -10px !important; 124 | } 125 | 126 | .fc-header-right .fc-button { 127 | margin: 0px !important; 128 | top: -50px !important; 129 | } 130 | 131 | .fc-state-active, .fc-state-active .fc-button-inner, .fc-state-hover, .fc-state-hover .fc-button-inner { 132 | background: none !important; 133 | color: #FFFFFF !important; 134 | } 135 | 136 | .fc-state-default, .fc-state-default .fc-button-inner { 137 | background: none !important; 138 | } 139 | 140 | .fc-button { 141 | border: none !important; 142 | margin-right: 2px; 143 | } 144 | 145 | .fc-view { 146 | top: 0px !important; 147 | } 148 | 149 | .fc-button .fc-button-inner { 150 | margin: 0px !important; 151 | padding: 2px !important; 152 | border: none !important; 153 | margin-right: 2px !important; 154 | background-color: #fafafa !important; 155 | background-image: -moz-linear-gradient(top, #fafafa, #efefef) !important; 156 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fafafa), to(#efefef)) !important; 157 | background-image: -webkit-linear-gradient(top, #fafafa, #efefef) !important; 158 | background-image: -o-linear-gradient(top, #fafafa, #efefef) !important; 159 | background-image: linear-gradient(to bottom, #fafafa, #efefef) !important; 160 | filter: progid:dximagetransform.microsoft.gradient(startColorstr='#fafafa', endColorstr='#efefef', GradientType=0) !important; 161 | -webkit-box-shadow: 0 1px 0px rgba(255, 255, 255, .8) !important; 162 | -moz-box-shadow: 0 1px 0px rgba(255, 255, 255, .8) !important; 163 | box-shadow: 0 1px 0px rgba(255, 255, 255, .8) !important; 164 | -webkit-border-radius: 3px !important; 165 | -moz-border-radius: 3px !important; 166 | border-radius: 3px !important; 167 | color: #646464 !important; 168 | border: 1px solid #ddd !important; 169 | text-shadow: 0 1px 0px rgba(255, 255, 255, .6) !important; 170 | text-align: center; 171 | } 172 | 173 | .fc-button.fc-state-disabled .fc-button-inner { 174 | color: #bcbbbb !important; 175 | } 176 | 177 | .fc-button.fc-state-active .fc-button-inner { 178 | background-color: #e5e4e4 !important; 179 | background-image: -moz-linear-gradient(top, #e5e4e4, #dddcdc) !important; 180 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#e5e4e4), to(#dddcdc)) !important; 181 | background-image: -webkit-linear-gradient(top, #e5e4e4, #dddcdc) !important; 182 | background-image: -o-linear-gradient(top, #e5e4e4, #dddcdc) !important; 183 | background-image: linear-gradient(to bottom, #e5e4e4, #dddcdc) !important; 184 | filter: progid:dximagetransform.microsoft.gradient(startColorstr='#e5e4e4', endColorstr='#dddcdc', GradientType=0) !important; 185 | } 186 | 187 | .fc-content { 188 | margin-top: 50px; 189 | } 190 | 191 | .fc-header-title h2 { 192 | line-height: 40px !important; 193 | font-size: 12px !important; 194 | } 195 | 196 | .fc-header { 197 | margin-bottom:0px !important; 198 | } 199 | 200 | /*--*/ 201 | 202 | /*.chart-position {*/ 203 | /*margin-top: 0px;*/ 204 | /*}*/ 205 | 206 | .stepy-titles li { 207 | margin: 10px 3px; 208 | } 209 | 210 | /*-----*/ 211 | .custom-bar-chart { 212 | margin-bottom: 40px; 213 | } 214 | 215 | /*menu icon plus minus*/ 216 | .dcjq-icon { 217 | top: 10px; 218 | } 219 | ul.sidebar-menu li ul.sub li a { 220 | padding: 0; 221 | } 222 | 223 | /*---*/ 224 | 225 | .img-responsive { 226 | width: 100%; 227 | } 228 | 229 | } 230 | 231 | 232 | 233 | @media (max-width: 480px) { 234 | 235 | .notify-row, .search, .dont-show , .inbox-head .sr-input, .inbox-head .sr-btn{ 236 | display: none; 237 | } 238 | 239 | #top_menu .nav > li, ul.top-menu > li { 240 | float: right; 241 | } 242 | .hidden-phone { 243 | display: none !important; 244 | } 245 | 246 | .chart-position { 247 | margin-top: 0px; 248 | } 249 | 250 | .navbar-inverse .navbar-toggle:hover, .navbar-inverse .navbar-toggle:focus { 251 | background-color: #ccc; 252 | border-color:#ccc ; 253 | } 254 | 255 | } 256 | 257 | @media (max-width:320px) { 258 | .login-social-link a { 259 | padding: 15px 17px !important; 260 | } 261 | 262 | .notify-row, .search, .dont-show, .inbox-head .sr-input, .inbox-head .sr-btn { 263 | display: none; 264 | } 265 | 266 | #top_menu .nav > li, ul.top-menu > li { 267 | float: right; 268 | } 269 | 270 | .hidden-phone { 271 | display: none !important; 272 | } 273 | 274 | .chart-position { 275 | margin-top: 0px; 276 | } 277 | 278 | .lock-wrapper { 279 | margin: 10% auto; 280 | max-width: 310px; 281 | } 282 | .lock-input { 283 | width: 82%; 284 | } 285 | 286 | .cmt-form { 287 | display: inline-block; 288 | width: 75%; 289 | } 290 | 291 | } 292 | 293 | 294 | 295 | 296 | -------------------------------------------------------------------------------- /app/assets/css/table-responsive.css: -------------------------------------------------------------------------------- 1 | /*Unseen Column*/ 2 | @media only screen and (max-width: 800px) { 3 | #unseen table td:nth-child(2), 4 | #unseen table th:nth-child(2) {display: none;} 5 | } 6 | 7 | @media only screen and (max-width: 640px) { 8 | #unseen table td:nth-child(4), 9 | #unseen table th:nth-child(4), 10 | #unseen table td:nth-child(7), 11 | #unseen table th:nth-child(7), 12 | #unseen table td:nth-child(8), 13 | #unseen table th:nth-child(8){display: none;} 14 | } 15 | 16 | /*flip-scroll*/ 17 | 18 | @media only screen and (max-width: 800px) { 19 | #flip-scroll .cf:after { visibility: hidden; display: block; font-size: 0; content: " "; clear: both; height: 0; } 20 | #flip-scroll * html .cf { zoom: 1; } 21 | #flip-scroll *:first-child+html .cf { zoom: 1; } 22 | #flip-scroll table { width: 100%; border-collapse: collapse; border-spacing: 0; } 23 | 24 | #flip-scroll th, 25 | #flip-scroll td { margin: 0; vertical-align: top; } 26 | #flip-scroll th { text-align: left; } 27 | #flip-scroll table { display: block; position: relative; width: 100%; } 28 | #flip-scroll thead { display: block; float: left; } 29 | #flip-scroll tbody { display: block; width: auto; position: relative; overflow-x: auto; white-space: nowrap; } 30 | #flip-scroll thead tr { display: block; } 31 | #flip-scroll th { display: block; text-align: right; } 32 | #flip-scroll tbody tr { display: inline-block; vertical-align: top; } 33 | #flip-scroll td { display: block; min-height: 1.25em; text-align: left; } 34 | 35 | 36 | /* sort out borders */ 37 | 38 | #flip-scroll th { border-bottom: 0; border-left: 0; } 39 | #flip-scroll td { border-left: 0; border-right: 0; border-bottom: 0; } 40 | #flip-scroll tbody tr { border-left: 1px solid #babcbf; } 41 | #flip-scroll th:last-child, 42 | #flip-scroll td:last-child { border-bottom: 1px solid #babcbf; } 43 | } 44 | 45 | /*no more table*/ 46 | 47 | @media only screen and (max-width: 800px) { 48 | /* Force table to not be like tables anymore */ 49 | #no-more-tables table, 50 | #no-more-tables thead, 51 | #no-more-tables tbody, 52 | #no-more-tables th, 53 | #no-more-tables td, 54 | #no-more-tables tr { 55 | display: block; 56 | } 57 | 58 | /* Hide table headers (but not display: none;, for accessibility) */ 59 | #no-more-tables thead tr { 60 | position: absolute; 61 | top: -9999px; 62 | left: -9999px; 63 | } 64 | 65 | #no-more-tables tr { border: 1px solid #ccc; } 66 | 67 | #no-more-tables td { 68 | /* Behave like a "row" */ 69 | border: none; 70 | border-bottom: 1px solid #eee; 71 | position: relative; 72 | padding-left: 50%; 73 | white-space: normal; 74 | text-align:left; 75 | } 76 | 77 | #no-more-tables td:before { 78 | /* Now like a table header */ 79 | position: absolute; 80 | /* Top/left values mimic padding */ 81 | top: 6px; 82 | left: 6px; 83 | width: 45%; 84 | padding-right: 10px; 85 | white-space: nowrap; 86 | text-align:left; 87 | font-weight: bold; 88 | } 89 | 90 | /* 91 | Label the data 92 | */ 93 | #no-more-tables td:before { content: attr(data-title); } 94 | } -------------------------------------------------------------------------------- /app/assets/css/to-do.css: -------------------------------------------------------------------------------- 1 | /*--------------Tasks Widget--------------*/ 2 | 3 | .task-content { 4 | margin-bottom: 30px; 5 | } 6 | 7 | .task-panel { 8 | background: #fff; 9 | text-align: left; 10 | box-shadow: 0px 3px 2px #aab2bd; 11 | margin: 5px; 12 | } 13 | 14 | .tasks-widget .task-content:after { 15 | clear: both; 16 | } 17 | 18 | .tasks-widget .task-footer { 19 | margin-top: 5px; 20 | } 21 | 22 | .tasks-widget .task-footer:after, 23 | .tasks-widget .task-footer:before { 24 | content: ""; 25 | display: table; 26 | line-height: 0; 27 | } 28 | 29 | .tasks-widget .task-footer:after { 30 | clear: both; 31 | } 32 | 33 | .tasks-widget .task-list { 34 | padding:0; 35 | margin:0; 36 | } 37 | 38 | .tasks-widget .task-list > li { 39 | position:relative; 40 | padding:10px 5px; 41 | border-bottom:1px dashed #eaeaea; 42 | } 43 | 44 | .tasks-widget .task-list li.last-line { 45 | border-bottom:none; 46 | } 47 | 48 | .tasks-widget .task-list li > .task-bell { 49 | margin-left:10px; 50 | } 51 | 52 | .tasks-widget .task-list li > .task-checkbox { 53 | float:left; 54 | width:30px; 55 | } 56 | 57 | .tasks-widget .task-list li > .task-title { 58 | overflow:hidden; 59 | margin-right:10px; 60 | } 61 | 62 | .tasks-widget .task-list li > .task-config { 63 | position:absolute; 64 | top:10px; 65 | right:10px; 66 | } 67 | 68 | .tasks-widget .task-list li .task-title .task-title-sp { 69 | margin-right:5px; 70 | } 71 | 72 | .tasks-widget .task-list li.task-done .task-title-sp { 73 | text-decoration:line-through; 74 | color: #bbbbbb; 75 | } 76 | 77 | .tasks-widget .task-list li.task-done { 78 | background:#f6f6f6; 79 | } 80 | 81 | .tasks-widget .task-list li.task-done:hover { 82 | background:#f4f4f4; 83 | } 84 | 85 | .tasks-widget .task-list li:hover { 86 | background:#f9f9f9; 87 | } 88 | 89 | .tasks-widget .task-list li .task-config { 90 | display:none; 91 | } 92 | 93 | .tasks-widget .task-list li:hover > .task-config { 94 | display:block; 95 | margin-bottom:0 !important; 96 | } 97 | 98 | 99 | @media only screen and (max-width: 320px) { 100 | 101 | .tasks-widget .task-config-btn { 102 | float:inherit; 103 | display:block; 104 | } 105 | 106 | .tasks-widget .task-list-projects li > .label { 107 | margin-bottom:5px; 108 | } 109 | 110 | } -------------------------------------------------------------------------------- /app/components/Main.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import Login from '../login/LoginComponent'; 3 | import NavBar from '../components/navbar'; 4 | import Notification from '../components/notification'; 5 | import Aside from '../components/aside'; 6 | import SettingsStoreHEC from '../hec/settings'; 7 | import {FatalErrorStoreHEC} from '../errors/hec'; 8 | import _ from 'lodash'; 9 | import {FatalError} from '../components/errors'; 10 | 11 | 12 | // Styles 13 | import {bootstrap} from '../../bower_components/bootstrap/dist/css/bootstrap.css'; 14 | import {font_awesome} from '../../bower_components/font-awesome/css/font-awesome.css'; 15 | import {responsive} from '../assets/css/style-responsive.css'; 16 | import {style} from '../assets/css/style.css'; 17 | import store from '../store'; 18 | 19 | class Main extends React.Component { 20 | 21 | render = () => { 22 | let settings = this.props.settings; 23 | 24 | if (settings == undefined) { 25 | return
; 26 | } 27 | 28 | let login_children = this.props.children.type == Login; 29 | 30 | if(login_children == true) { 31 | var navbar = undefined; 32 | var aside = undefined; 33 | var notification = undefined; 34 | } else { 35 | var navbar = ; 36 | var aside =