Compare commits

...

35 Commits
0.8.0 ... 0.8.1

Author SHA1 Message Date
Min RK
8b583cb445 release 0.8.1 2017-11-07 13:39:10 +01:00
Min RK
038a85af43 add removal of bower to changelog for 0.8.1 2017-11-07 13:39:10 +01:00
Min RK
9165beb41c Merge pull request #1306 from minrk/bower-lite
remove bower
2017-11-07 13:35:08 +01:00
Min RK
b285de4412 npm install with unsafe-perm on docker
npm doesn't like to run postinstall as root
2017-11-07 13:01:31 +01:00
Min RK
5826035fe9 node when css building starts 2017-11-07 12:59:57 +01:00
Min RK
b953ac295b check for built css as well in data-files lookup
avoids serving incomplete files
2017-11-07 11:40:24 +01:00
Min RK
8a95066b2e run lessc via npm 2017-11-07 11:39:53 +01:00
Min RK
00a4aef607 remove bower
use npm to fetch dependencies and a simple postinstall script to copy into components
2017-11-07 11:38:47 +01:00
Carol Willing
e01ce7b665 Merge pull request #1516 from minrk/0.8.1-changes
changelog for 0.8.1
2017-11-06 14:23:03 -08:00
Min RK
a57df48f28 changelog for 0.8.1 2017-11-03 11:07:08 +01:00
Min RK
5d7e008055 Merge pull request #1512 from DeepHorizons/update-service-doc
Update docs and examples for the slash at the end of the prefix
2017-11-03 10:31:46 +01:00
Min RK
ba31b3ecb7 Merge pull request #1497 from DeepHorizons/update-docs
[doc] Updated the rest docs to about change in 0.8
2017-11-01 11:49:41 +01:00
Joshua Milas
3c5eb934bf Update docs and examples for the slash at the end of the prefix 2017-10-31 14:04:37 -04:00
Joshua Milas
82e15df6e9 Added that you can also access the notebook if you are the owner 2017-10-31 10:15:02 -04:00
Yuvi Panda
e3c83c0c29 Merge pull request #1509 from minrk/stacky
copy exception before reraising
2017-10-30 17:50:37 -07:00
Min RK
94542334c4 Merge pull request #1507 from minrk/upgrade-db
add `jupyterhub --upgrade-db` to trigger upgrade on launch
2017-10-30 15:34:31 +01:00
Min RK
95494b3ace only sqlite 2017-10-30 09:58:55 +01:00
Min RK
a131cfb79e add jupyterhub --upgrade-db to trigger upgrade on launch
Upgrades the database (if needed) on start.

This is opt-in, for uses like the helm chart where explicit 'upgrade-db' steps are hard to insert.

This ought to be safe for sqlite users, where an automatic backup file is created *if an upgrade will occur*.
2017-10-27 15:35:17 +02:00
Min RK
f002c67343 add dbutil.upgrade_if_needed
so it's reusable now that we want to use it in more than one place
2017-10-27 15:35:17 +02:00
Min RK
b9caf95c72 copy exception before reraising
avoids growing traceback on each raise
2017-10-27 15:29:16 +02:00
Min RK
5356954240 Merge pull request #1493 from schon/rendering-logout
Rendering logout page when auto login is true
2017-10-27 14:42:22 +02:00
Joshua Milas
126c73002e Updated the rest docs to about change in 0.8
In 0.8, the jupyterhub api token can also be used to make requests to
hte jupyter notebook given some conditions. This commit updates that
documentation
2017-10-21 09:18:08 -04:00
Seongduk Cheon
65b4502a78 Rendering logout page when auto login is true 2017-10-19 12:14:20 +09:00
Min RK
3406161d75 Merge pull request #1460 from DeepHorizons/asyncio_event_loop
Enable the asyncio event loop to run with tornado
2017-10-13 11:18:02 +02:00
Yuvi Panda
e45f00f0f7 Merge pull request #1475 from minrk/disallow-slash
disallow '/' in usernames
2017-10-11 08:54:58 -07:00
Min RK
71f4a30562 Merge pull request #1473 from franga2000/patch-1
Change username input type to "text" to fix auto-filling
2017-10-11 16:01:45 +02:00
Min RK
20ba414b41 disallow / in usernames 2017-10-11 11:46:50 +02:00
Miha Frangez
f5250f04c5 Change username input type to "text"
<input type="username"> isn't allowed according to the spec and causes problems with autofill in Firefox.
2017-10-10 22:30:20 +02:00
Carol Willing
c2ea20a87a Merge pull request #1466 from minrk/auth_state_username
[doc] typo in auth_state structure
2017-10-04 16:57:27 -04:00
Carol Willing
b14989d4a5 Merge pull request #1465 from minrk/outerjoin
use outerjoin to join User with Spawner for admin page
2017-10-04 16:56:52 -04:00
Min RK
04578e329c typo in auth_state structure
it's 'name' not 'username'
2017-10-04 14:18:14 +02:00
Min RK
be05e438ca use outerjoin to join User with Spawner for admin page
avoids excluding users from admin page if they haven't instantiated their first Spawner yet
2017-10-04 13:58:08 +02:00
Min RK
24d9215029 back to dev 2017-10-03 21:49:23 +02:00
Joshua Milas
54dcca7ba9 Install the AsyncIOMainLoop instead of configuring it.
Installing the loop instructs the tornado loop to point to the ayncio loop and use
that. IOLoop.configure told the tornado loop to create a new ioloop when
a loop was needed, which is not what we want.
2017-10-03 08:30:49 -04:00
Joshua Milas
056a7351a3 Enable the asyncio event loop to run with tornado
This allows packages written to use asyncio to work with tornado
2017-09-27 23:04:00 -04:00
24 changed files with 219 additions and 116 deletions

View File

@@ -1,3 +0,0 @@
{
"directory": "share/jupyter/hub/static/components"
}

View File

@@ -52,7 +52,8 @@ ENV PATH=/opt/conda/bin:$PATH
ADD . /src/jupyterhub
WORKDIR /src/jupyterhub
RUN python setup.py js && pip install . && \
RUN npm install --unsafe-perm && \
pip install . && \
rm -rf $PWD ~/.cache ~/.npm
RUN mkdir -p /srv/jupyterhub/

View File

@@ -1,7 +1,7 @@
include README.md
include COPYING.md
include setupegg.py
include bower.json
include bower-lite
include package.json
include *requirements.txt
include Dockerfile
@@ -18,12 +18,13 @@ graft docs
prune docs/node_modules
# prune some large unused files from components
prune share/jupyter/hub/static/components/bootstrap/css
exclude share/jupyter/hub/static/components/components/fonts/*.svg
exclude share/jupyter/hub/static/components/bootstrap/less/*.js
exclude share/jupyter/hub/static/components/font-awesome/css
prune share/jupyter/hub/static/components/bootstrap/dist/css
exclude share/jupyter/hub/static/components/bootstrap/dist/fonts/*.svg
prune share/jupyter/hub/static/components/font-awesome/css
prune share/jupyter/hub/static/components/font-awesome/scss
exclude share/jupyter/hub/static/components/font-awesome/fonts/*.svg
exclude share/jupyter/hub/static/components/jquery/*migrate*.js
prune share/jupyter/hub/static/components/jquery/external
prune share/jupyter/hub/static/components/jquery/src
prune share/jupyter/hub/static/components/moment/lang
prune share/jupyter/hub/static/components/moment/min

36
bower-lite Executable file
View File

@@ -0,0 +1,36 @@
#!/usr/bin/env python
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
"""
bower-lite
Since Bower's on its way out,
stage frontend dependencies from node_modules into components
"""
import json
import os
from os.path import join
import shutil
HERE = os.path.abspath(os.path.dirname(__file__))
components = join(HERE, "share", "jupyter", "hub", "static", "components")
node_modules = join(HERE, "node_modules")
if os.path.exists(components):
shutil.rmtree(components)
os.mkdir(components)
with open(join(HERE, 'package.json')) as f:
package_json = json.load(f)
dependencies = package_json['dependencies']
for dep in dependencies:
src = join(node_modules, dep)
dest = join(components, dep)
print("%s -> %s" % (src, dest))
shutil.copytree(src, dest)

View File

@@ -1,11 +0,0 @@
{
"name": "jupyterhub-deps",
"version": "0.0.0",
"dependencies": {
"bootstrap": "components/bootstrap#~3.3",
"font-awesome": "components/font-awesome#~4.7",
"jquery": "components/jquery#~3.2",
"moment": "~2.18",
"requirejs": "~2.3"
}
}

View File

@@ -7,7 +7,32 @@ command line for details.
## [Unreleased]
## [0.8.0] 2017-10-03
## 0.8
### [0.8.1] 2017-11-07
JupyterHub 0.8.1 is a collection of bugfixes and small improvements on 0.8.
#### Added
- Run tornado with AsyncIO by default
- Add `jupyterhub --upgrade-db` flag for automatically upgrading the database as part of startup.
This is useful for cases where manually running `jupyterhub upgrade-db`
as a separate step is unwieldy.
- Avoid creating backups of the database when no changes are to be made by
`jupyterhub upgrade-db`.
#### Fixed
- Add some further validation to usernames - `/` is not allowed in usernames.
- Fix empty logout page when using auto_login
- Fix autofill of username field in default login form.
- Fix listing of users on the admin page who have not yet started their server.
- Fix ever-growing traceback when re-raising Exceptions from spawn failures.
- Remove use of deprecated `bower` for javascript client dependencies.
### [0.8.0] 2017-10-03
JupyterHub 0.8 is a big release!
@@ -235,7 +260,8 @@ Fix removal of `/login` page in 0.4.0, breaking some OAuth providers.
First preview release
[Unreleased]: https://github.com/jupyterhub/jupyterhub/compare/0.8.0...HEAD
[Unreleased]: https://github.com/jupyterhub/jupyterhub/compare/0.8.1...HEAD
[0.8.1]: https://github.com/jupyterhub/jupyterhub/compare/0.8.0...0.8.1
[0.8.0]: https://github.com/jupyterhub/jupyterhub/compare/0.7.2...0.8.0
[0.7.2]: https://github.com/jupyterhub/jupyterhub/compare/0.7.1...0.7.2
[0.7.1]: https://github.com/jupyterhub/jupyterhub/compare/0.7.0...0.7.1

View File

@@ -190,7 +190,7 @@ class MyAuthenticator(Authenticator):
username = yield identify_user(handler, data)
upstream_token = yield token_for_user(username)
return {
'username': username,
'name': username,
'auth_state': {
'upstream_token': upstream_token,
},

View File

@@ -114,10 +114,11 @@ r.raise_for_status()
r.json()
```
Note that the API token authorizes **JupyterHub** REST API requests. The same
token does **not** authorize access to the [Jupyter Notebook REST API][]
provided by notebook servers managed by JupyterHub. A different token is used
to access the **Jupyter Notebook** API.
The same API token can also authorize access to the [Jupyter Notebook REST API][]
provided by notebook servers managed by JupyterHub if one of the following is true:
1. The token is for the same user as the owner of the notebook
2. The token is tied to an admin user or service **and** `c.JupyterHub.admin_access` is set to `True`
## Enabling users to spawn multiple named-servers via the API

View File

@@ -178,7 +178,13 @@ When you run a service that has a url, it will be accessible under a
your service to route proxied requests properly, it must take
`JUPYTERHUB_SERVICE_PREFIX` into account when routing requests. For example, a
web service would normally service its root handler at `'/'`, but the proxied
service would need to serve `JUPYTERHUB_SERVICE_PREFIX + '/'`.
service would need to serve `JUPYTERHUB_SERVICE_PREFIX`.
Note that `JUPYTERHUB_SERVICE_PREFIX` will contain a trailing slash. This must
be taken into consideration when creating the service routes. If you include an
extra slash you might get unexpected behavior. For example if your service has a
`/foo` endpoint, the route would be `JUPYTERHUB_SERVICE_PREFIX + foo`, and
`/foo/bar` would be `JUPYTERHUB_SERVICE_PREFIX + foo/bar`.
## Hub Authentication and Services
@@ -269,7 +275,7 @@ def authenticated(f):
return decorated
@app.route(prefix + '/')
@app.route(prefix)
@authenticated
def whoami(user):
return Response(

View File

@@ -8,7 +8,7 @@ Uses `jupyterhub.services.HubAuth` to authenticate requests with the Hub in a [f
jupyterhub --ip=127.0.0.1
2. Visit http://127.0.0.1:8000/services/whoami or http://127.0.0.1:8000/services/whoami-oauth
2. Visit http://127.0.0.1:8000/services/whoami/ or http://127.0.0.1:8000/services/whoami-oauth/
After logging in with your local-system credentials, you should see a JSON dump of your user info:

View File

@@ -43,7 +43,7 @@ def authenticated(f):
return decorated
@app.route(prefix + '/')
@app.route(prefix)
@authenticated
def whoami(user):
return Response(

View File

@@ -13,7 +13,8 @@ def get_data_files():
# walk up, looking for prefix/share/jupyter
while path != '/':
share_jupyter = join(path, 'share', 'jupyter', 'hub')
if exists(join(share_jupyter, 'static', 'components')):
static = join(share_jupyter, 'static')
if all(exists(join(static, f)) for f in ['components', 'css']):
return share_jupyter
path, _ = split(path)
# didn't find it, give up

View File

@@ -6,7 +6,8 @@
version_info = (
0,
8,
0,
1,
# 'dev',
)
__version__ = '.'.join(map(str, version_info))

View File

@@ -12,7 +12,6 @@ import logging
from operator import itemgetter
import os
import re
import shutil
import signal
import sys
from textwrap import dedent
@@ -23,7 +22,6 @@ if sys.version_info[:2] < (3, 3):
from jinja2 import Environment, FileSystemLoader
from sqlalchemy import create_engine
from sqlalchemy.exc import OperationalError
from tornado.httpclient import AsyncHTTPClient
@@ -32,6 +30,8 @@ from tornado.ioloop import IOLoop, PeriodicCallback
from tornado.log import app_log, access_log, gen_log
import tornado.options
from tornado import gen, web
from tornado.platform.asyncio import AsyncIOMainLoop
AsyncIOMainLoop().install()
from traitlets import (
Unicode, Integer, Dict, TraitError, List, Bool, Any,
@@ -99,6 +99,13 @@ flags = {
'no-db': ({'JupyterHub': {'db_url': 'sqlite:///:memory:'}},
"disable persisting state database to disk"
),
'upgrade-db': ({'JupyterHub': {'upgrade_db': True}},
"""Automatically upgrade the database if needed on startup.
Only safe if the database has been backed up.
Only SQLite database files will be backed up automatically.
"""
),
'no-ssl': ({'JupyterHub': {'confirm_no_ssl': True}},
"[DEPRECATED in 0.7: does nothing]"
),
@@ -165,39 +172,11 @@ class UpgradeDB(Application):
aliases = common_aliases
classes = []
def _backup_db_file(self, db_file):
"""Backup a database file"""
if not os.path.exists(db_file):
return
timestamp = datetime.now().strftime('.%Y-%m-%d-%H%M%S')
backup_db_file = db_file + timestamp
for i in range(1, 10):
if not os.path.exists(backup_db_file):
break
backup_db_file = '{}.{}.{}'.format(db_file, timestamp, i)
if os.path.exists(backup_db_file):
self.exit("backup db file already exists: %s" % backup_db_file)
self.log.info("Backing up %s => %s", db_file, backup_db_file)
shutil.copy(db_file, backup_db_file)
def start(self):
hub = JupyterHub(parent=self)
hub.load_config_file(hub.config_file)
self.log = hub.log
if (hub.db_url.startswith('sqlite:///')):
db_file = hub.db_url.split(':///', 1)[1]
self._backup_db_file(db_file)
self.log.info("Upgrading %s", hub.db_url)
# run check-db-revision first
engine = create_engine(hub.db_url)
try:
orm.check_db_revision(engine)
except orm.DatabaseSchemaMismatch:
# ignore mismatch error because that's what we are here for!
pass
dbutil.upgrade(hub.db_url)
dbutil.upgrade_if_needed(hub.db_url, log=self.log)
class JupyterHub(Application):
@@ -634,6 +613,12 @@ class JupyterHub(Application):
"""
).tag(config=True)
upgrade_db = Bool(False,
help="""Upgrade the database automatically on start.
Only safe if database is regularly backed up.
Only SQLite databases will be backed up to a local file automatically.
""").tag(config=True)
reset_db = Bool(False,
help="Purge and reset the database."
).tag(config=True)
@@ -897,7 +882,11 @@ class JupyterHub(Application):
def init_db(self):
"""Create the database connection"""
self.log.debug("Connecting to db: %s", self.db_url)
if self.upgrade_db:
dbutil.upgrade_if_needed(self.db_url, log=self.log)
try:
self.session_factory = orm.new_session_factory(
self.db_url,

View File

@@ -144,6 +144,12 @@ class Authenticator(LoggingConfigurable):
Return True if username is valid, False otherwise.
"""
if '/' in username:
# / is not allowed in usernames
return False
if not username:
# empty usernames are not allowed
return False
if not self.username_regex:
return True
return bool(self.username_regex.match(username))

View File

@@ -5,11 +5,17 @@
# Based on pgcontents.utils.migrate, used under the Apache license.
from contextlib import contextmanager
from datetime import datetime
import os
import shutil
from subprocess import check_call
import sys
from tempfile import TemporaryDirectory
from sqlalchemy import create_engine
from . import orm
_here = os.path.abspath(os.path.dirname(__file__))
ALEMBIC_INI_TEMPLATE_PATH = os.path.join(_here, 'alembic.ini')
@@ -84,6 +90,46 @@ def upgrade(db_url, revision='head'):
)
def backup_db_file(db_file, log=None):
"""Backup a database file if it exists"""
timestamp = datetime.now().strftime('.%Y-%m-%d-%H%M%S')
backup_db_file = db_file + timestamp
for i in range(1, 10):
if not os.path.exists(backup_db_file):
break
backup_db_file = '{}.{}.{}'.format(db_file, timestamp, i)
#
if os.path.exists(backup_db_file):
raise OSError("backup db file already exists: %s" % backup_db_file)
if log:
log.info("Backing up %s => %s", db_file, backup_db_file)
shutil.copy(db_file, backup_db_file)
def upgrade_if_needed(db_url, backup=True, log=None):
"""Upgrade a database if needed
If the database is sqlite, a backup file will be created with a timestamp.
Other database systems should perform their own backups prior to calling this.
"""
# run check-db-revision first
engine = create_engine(db_url)
try:
orm.check_db_revision(engine)
except orm.DatabaseSchemaMismatch:
# ignore mismatch error because that's what we are here for!
pass
else:
# nothing to do
return
log.info("Upgrading %s", db_url)
# we need to upgrade, backup the database
if backup and db_url.startswith('sqlite:///'):
db_file = db_url.split(':///', 1)[1]
backup_db_file(db_file, log=log)
upgrade(db_url)
def _alembic(*args):
"""Run an alembic command with a temporary alembic.ini"""
with _temp_alembic_ini('sqlite:///jupyterhub.sqlite') as alembic_ini:

View File

@@ -3,6 +3,7 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import copy
import re
from datetime import timedelta
from http.client import responses
@@ -705,9 +706,11 @@ class UserSpawnHandler(BaseHandler):
# Condition: spawner not active and _spawn_future exists and contains an Exception
# Implicit spawn on /user/:name is not allowed if the user's last spawn failed.
# We should point the user to Home if the most recent spawn failed.
exc = spawner._spawn_future.exception()
self.log.error("Preventing implicit spawn for %s because last spawn failed: %s",
spawner._log_name, spawner._spawn_future.exception())
raise spawner._spawn_future.exception()
spawner._log_name, exc)
# raise a copy because each time an Exception object is re-raised, its traceback grows
raise copy.copy(exc).with_traceback(exc.__traceback__)
# check for pending spawn
if spawner.pending and spawner._spawn_future:

View File

@@ -20,7 +20,8 @@ class LogoutHandler(BaseHandler):
self.clear_login_cookie()
self.statsd.incr('logout')
if self.authenticator.auto_login:
self.render_template('logout.html')
html = self.render_template('logout.html')
self.finish(html)
else:
self.redirect(self.settings['login_url'], permanent=False)

View File

@@ -201,7 +201,7 @@ class AdminHandler(BaseHandler):
# get User.col.desc() order objects
ordered = [ getattr(c, o)() for c, o in zip(cols, orders) ]
users = self.db.query(orm.User).join(orm.Spawner).order_by(*ordered)
users = self.db.query(orm.User).outerjoin(orm.Spawner).order_by(*ordered)
users = [ self._user_from_orm(u) for u in users ]
running = [ u for u in users if u.running ]

View File

@@ -24,7 +24,6 @@ from sqlalchemy.pool import StaticPool
from sqlalchemy.sql.expression import bindparam
from sqlalchemy import create_engine, Table
from .dbutil import _temp_alembic_ini
from .utils import (
random_port,
new_token, hash_token, compare_token,
@@ -463,6 +462,8 @@ def check_db_revision(engine):
current_table_names = set(engine.table_names())
my_table_names = set(Base.metadata.tables.keys())
from .dbutil import _temp_alembic_ini
with _temp_alembic_ini(engine.url) as ini:
cfg = alembic.config.Config(ini)
scripts = ScriptDirectory.from_config(cfg)

View File

@@ -1,17 +1,27 @@
{
"name": "jupyterhub-deps",
"version": "0.0.0",
"description": "JupyterHub nodejs dependencies",
"author": "Jupyter Developers",
"license": "BSD",
"repository": {
"type": "git",
"url": "https://github.com/jupyter/jupyterhub.git"
},
"devDependencies": {
"bower": "*",
"less": "^2.7.1",
"less-plugin-clean-css": "^1.5.1",
"clean-css": "^3.4.13"
}
"name": "jupyterhub-deps",
"version": "0.0.0",
"description": "JupyterHub nodejs dependencies",
"author": "Jupyter Developers",
"license": "BSD",
"repository": {
"type": "git",
"url": "https://github.com/jupyter/jupyterhub.git"
},
"scripts": {
"postinstall": "./bower-lite",
"lessc": "lessc"
},
"devDependencies": {
"less": "^2.7.1",
"less-plugin-clean-css": "^1.5.1",
"clean-css": "^3.4.13"
},
"dependencies": {
"bootstrap": "^3.3.7",
"font-awesome": "^4.7.0",
"jquery": "^3.2.1",
"moment": "^2.18.1",
"requirejs": "^2.3.4"
}
}

View File

@@ -149,45 +149,34 @@ class BaseCommand(Command):
return []
class Bower(BaseCommand):
class NPM(BaseCommand):
description = "fetch static client-side components with bower"
user_options = []
bower_dir = pjoin(static, 'components')
node_modules = pjoin(here, 'node_modules')
bower_dir = pjoin(static, 'components')
def should_run(self):
if not os.path.exists(self.bower_dir):
return True
return mtime(self.bower_dir) < mtime(pjoin(here, 'bower.json'))
def should_run_npm(self):
if not shutil.which('npm'):
print("npm unavailable", file=sys.stderr)
return False
if not os.path.exists(self.bower_dir):
return True
if not os.path.exists(self.node_modules):
return True
if mtime(self.bower_dir) < mtime(self.node_modules):
return True
return mtime(self.node_modules) < mtime(pjoin(here, 'package.json'))
def run(self):
if not self.should_run():
print("bower dependencies up to date")
print("npm dependencies up to date")
return
if self.should_run_npm():
print("installing build dependencies with npm")
check_call(['npm', 'install', '--progress=false'], cwd=here, shell=shell)
os.utime(self.node_modules)
print("installing js dependencies with npm")
check_call(['npm', 'install', '--progress=false'], cwd=here, shell=shell)
os.utime(self.node_modules)
env = os.environ.copy()
env['PATH'] = npm_path
args = ['bower', 'install', '--allow-root', '--config.interactive=false']
try:
check_call(args, cwd=here, env=env, shell=shell)
except OSError as e:
print("Failed to run bower: %s" % e, file=sys.stderr)
print("You can install js dependencies with `npm install`", file=sys.stderr)
raise
os.utime(self.bower_dir)
# update data-files in case this created new files
self.distribution.data_files = get_data_files()
@@ -225,22 +214,21 @@ class CSS(BaseCommand):
return
self.run_command('js')
print("Building css with less")
style_less = pjoin(static, 'less', 'style.less')
style_css = pjoin(static, 'css', 'style.min.css')
sourcemap = style_css + '.map'
env = os.environ.copy()
env['PATH'] = npm_path
args = [
'lessc', '--clean-css',
'npm', 'run', 'lessc', '--', '--clean-css',
'--source-map-basepath={}'.format(static),
'--source-map={}'.format(sourcemap),
'--source-map-rootpath=../',
style_less, style_css,
]
try:
check_call(args, cwd=here, env=env, shell=shell)
check_call(args, cwd=here, shell=shell)
except OSError as e:
print("Failed to run lessc: %s" % e, file=sys.stderr)
print("You can install js dependencies with `npm install`", file=sys.stderr)
@@ -275,7 +263,7 @@ class bdist_egg_disabled(bdist_egg):
setup_args['cmdclass'] = {
'js': Bower,
'js': NPM,
'css': CSS,
'build_py': js_css_first(build_py, strict=is_repo),
'sdist': js_css_first(sdist, strict=True),

View File

@@ -35,7 +35,7 @@
<label for="username_input">Username:</label>
<input
id="username_input"
type="username"
type="text"
autocapitalize="off"
autocorrect="off"
class="form-control"

View File

@@ -35,8 +35,8 @@
<link rel="stylesheet" href="{{ static_url("css/style.min.css") }}" type="text/css"/>
{% endblock %}
<script src="{{static_url("components/requirejs/require.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{static_url("components/jquery/jquery.min.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{static_url("components/bootstrap/js/bootstrap.min.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{static_url("components/jquery/dist/jquery.min.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{static_url("components/bootstrap/dist/js/bootstrap.min.js") }}" type="text/javascript" charset="utf-8"></script>
<script>
require.config({
{% if version_hash %}
@@ -45,8 +45,8 @@
baseUrl: '{{static_url("js", include_version=False)}}',
paths: {
components: '../components',
jquery: '../components/jquery/jquery.min',
bootstrap: '../components/bootstrap/js/bootstrap.min',
jquery: '../components/jquery/dist/jquery.min',
bootstrap: '../components/bootstrap/dist/js/bootstrap.min',
moment: "../components/moment/moment",
},
shim: {