From 0f5060eee34a70015b220a2983819c56a41a3826 2012-03-09 06:19:12 From: Brian E. Granger Date: 2012-03-09 06:19:12 Subject: [PATCH] Merge pull request #1383 from ellisonbg/nbparallel IPython clusters can now be managed using the Notebook. --- diff --git a/IPython/config/loader.py b/IPython/config/loader.py index d7db801..4956c18 100644 --- a/IPython/config/loader.py +++ b/IPython/config/loader.py @@ -676,3 +676,27 @@ class KVArgParseConfigLoader(ArgParseConfigLoader): sub_parser.load_config(self.extra_args) self.config._merge(sub_parser.config) self.extra_args = sub_parser.extra_args + + +def load_pyconfig_files(config_files, path): + """Load multiple Python config files, merging each of them in turn. + + Parameters + ========== + config_files : list of str + List of config files names to load and merge into the config. + path : unicode + The full path to the location of the config files. + """ + config = Config() + for cf in config_files: + loader = PyFileConfigLoader(cf, path=path) + try: + next_config = loader.load_config() + except ConfigFileNotFound: + pass + except: + raise + else: + config._merge(next_config) + return config diff --git a/IPython/core/profileapp.py b/IPython/core/profileapp.py index 021b58a..b09b0aa 100644 --- a/IPython/core/profileapp.py +++ b/IPython/core/profileapp.py @@ -92,6 +92,29 @@ ipython profile list -h # show the help string for the list subcommand #----------------------------------------------------------------------------- +def list_profiles_in(path): + """list profiles in a given root directory""" + files = os.listdir(path) + profiles = [] + for f in files: + full_path = os.path.join(path, f) + if os.path.isdir(full_path) and f.startswith('profile_'): + profiles.append(f.split('_',1)[-1]) + return profiles + + +def list_bundled_profiles(): + """list profiles that are bundled with IPython.""" + path = os.path.join(get_ipython_package_dir(), u'config', u'profile') + files = os.listdir(path) + profiles = [] + for profile in files: + full_path = os.path.join(path, profile) + if os.path.isdir(full_path): + profiles.append(profile) + return profiles + + class ProfileList(Application): name = u'ipython-profile' description = list_help @@ -115,35 +138,15 @@ class ProfileList(Application): the environment variable IPYTHON_DIR. """ ) - - def _list_profiles_in(self, path): - """list profiles in a given root directory""" - files = os.listdir(path) - profiles = [] - for f in files: - full_path = os.path.join(path, f) - if os.path.isdir(full_path) and f.startswith('profile_'): - profiles.append(f.split('_',1)[-1]) - return profiles - - def _list_bundled_profiles(self): - """list profiles in a given root directory""" - path = os.path.join(get_ipython_package_dir(), u'config', u'profile') - files = os.listdir(path) - profiles = [] - for profile in files: - full_path = os.path.join(path, profile) - if os.path.isdir(full_path): - profiles.append(profile) - return profiles - + + def _print_profiles(self, profiles): """print list of profiles, indented.""" for profile in profiles: print ' %s' % profile - + def list_profile_dirs(self): - profiles = self._list_bundled_profiles() + profiles = list_bundled_profiles() if profiles: print print "Available profiles in IPython:" @@ -153,13 +156,13 @@ class ProfileList(Application): print " into your IPython directory (%s)," % self.ipython_dir print " where you can customize it." - profiles = self._list_profiles_in(self.ipython_dir) + profiles = list_profiles_in(self.ipython_dir) if profiles: print print "Available profiles in %s:" % self.ipython_dir self._print_profiles(profiles) - profiles = self._list_profiles_in(os.getcwdu()) + profiles = list_profiles_in(os.getcwdu()) if profiles: print print "Available profiles in current directory (%s):" % os.getcwdu() diff --git a/IPython/core/tests/test_profile.py b/IPython/core/tests/test_profile.py index d3bbfe7..ddfd8d6 100644 --- a/IPython/core/tests/test_profile.py +++ b/IPython/core/tests/test_profile.py @@ -1,3 +1,4 @@ +# coding: utf-8 """Tests for profile-related functions. Currently only the startup-dir functionality is tested, but more tests should @@ -25,9 +26,12 @@ import shutil import sys import tempfile +from unittest import TestCase + import nose.tools as nt from nose import SkipTest +from IPython.core.profileapp import list_profiles_in, list_bundled_profiles from IPython.core.profiledir import ProfileDir from IPython.testing import decorators as dec @@ -79,35 +83,69 @@ def win32_without_pywin32(): return False -@dec.skipif(win32_without_pywin32(), "Test requires pywin32 on Windows") -def test_startup_py(): - # create profile dir - pd = ProfileDir.create_profile_dir_by_name(IP_TEST_DIR, 'test') - # write startup python file - with open(os.path.join(pd.startup_dir, '00-start.py'), 'w') as f: - f.write('zzz=123\n') - # write simple test file, to check that the startup file was run - fname = os.path.join(TMP_TEST_DIR, 'test.py') - with open(fname, 'w') as f: - f.write(py3compat.doctest_refactor_print('print zzz\n')) - # validate output - tt.ipexec_validate(fname, '123', '', - options=['--ipython-dir', IP_TEST_DIR, '--profile', 'test']) - -@dec.skipif(win32_without_pywin32(), "Test requires pywin32 on Windows") -def test_startup_ipy(): - # create profile dir - pd = ProfileDir.create_profile_dir_by_name(IP_TEST_DIR, 'test') - # write startup ipython file - with open(os.path.join(pd.startup_dir, '00-start.ipy'), 'w') as f: - f.write('%profile\n') - # write empty script, because we don't need anything to happen - # after the startup file is run - fname = os.path.join(TMP_TEST_DIR, 'test.py') - with open(fname, 'w') as f: - f.write('') - # validate output - tt.ipexec_validate(fname, 'test', '', - options=['--ipython-dir', IP_TEST_DIR, '--profile', 'test']) +class ProfileStartupTest(TestCase): + def setUp(self): + # create profile dir + self.pd = ProfileDir.create_profile_dir_by_name(IP_TEST_DIR, 'test') + self.options = ['--ipython-dir', IP_TEST_DIR, '--profile', 'test'] + self.fname = os.path.join(TMP_TEST_DIR, 'test.py') + + def tearDown(self): + # We must remove this profile right away so its presence doesn't + # confuse other tests. + shutil.rmtree(self.pd.location) + + def init(self, startup_file, startup, test): + # write startup python file + with open(os.path.join(self.pd.startup_dir, startup_file), 'w') as f: + f.write(startup) + # write simple test file, to check that the startup file was run + with open(self.fname, 'w') as f: + f.write(py3compat.doctest_refactor_print(test)) + + def validate(self, output): + tt.ipexec_validate(self.fname, output, '', options=self.options) + + @dec.skipif(win32_without_pywin32(), "Test requires pywin32 on Windows") + def test_startup_py(self): + self.init('00-start.py', 'zzz=123\n', + py3compat.doctest_refactor_print('print zzz\n')) + self.validate('123') + + @dec.skipif(win32_without_pywin32(), "Test requires pywin32 on Windows") + def test_startup_ipy(self): + self.init('00-start.ipy', '%profile\n', '') + self.validate('test') + +def test_list_profiles_in(): + # No need to remove these directories and files, as they will get nuked in + # the module-level teardown. + td = tempfile.mkdtemp(dir=TMP_TEST_DIR) + td = py3compat.str_to_unicode(td) + for name in ('profile_foo', u'profile_ünicode', 'profile_hello', + 'not_a_profile'): + os.mkdir(os.path.join(td, name)) + with open(os.path.join(td, 'profile_file'), 'w') as f: + f.write("I am not a profile directory") + profiles = list_profiles_in(td) + # unicode normalization can turn u'ünicode' into u'u\0308nicode', + # so only check for *nicode, and that creating a ProfileDir from the + # name remains valid + found_unicode = False + for p in list(profiles): + if p.endswith('nicode'): + pd = ProfileDir.find_profile_dir_by_name(td, p) + profiles.remove(p) + found_unicode = True + break + nt.assert_true(found_unicode) + nt.assert_equals(set(profiles), set(['foo', 'hello'])) + + +def test_list_bundled_profiles(): + # This variable will need to be updated when a new profile gets bundled + bundled_true = [u'cluster', u'math', u'pysh', u'python3', u'sympy'] + bundled = sorted(list_bundled_profiles()) + nt.assert_equals(bundled, bundled_true) diff --git a/IPython/frontend/html/notebook/clustermanager.py b/IPython/frontend/html/notebook/clustermanager.py new file mode 100644 index 0000000..6a28fa4 --- /dev/null +++ b/IPython/frontend/html/notebook/clustermanager.py @@ -0,0 +1,175 @@ +"""Manage IPython.parallel clusters in the notebook. + +Authors: + +* Brian Granger +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2011 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import os + +from tornado import web +from zmq.eventloop import ioloop + +from IPython.config.configurable import LoggingConfigurable +from IPython.config.loader import load_pyconfig_files +from IPython.utils.traitlets import Dict, Instance, CFloat +from IPython.parallel.apps.ipclusterapp import IPClusterStart +from IPython.core.profileapp import list_profiles_in +from IPython.core.profiledir import ProfileDir +from IPython.utils.path import get_ipython_dir +from IPython.utils.sysinfo import num_cpus + + +#----------------------------------------------------------------------------- +# Classes +#----------------------------------------------------------------------------- + + +class DummyIPClusterStart(IPClusterStart): + """Dummy subclass to skip init steps that conflict with global app. + + Instantiating and initializing this class should result in fully configured + launchers, but no other side effects or state. + """ + + def init_signal(self): + pass + def init_logging(self): + pass + def reinit_logging(self): + pass + + +class ClusterManager(LoggingConfigurable): + + profiles = Dict() + + delay = CFloat(1., config=True, + help="delay (in s) between starting the controller and the engines") + + loop = Instance('zmq.eventloop.ioloop.IOLoop') + def _loop_default(self): + from zmq.eventloop.ioloop import IOLoop + return IOLoop.instance() + + def build_launchers(self, profile_dir): + starter = DummyIPClusterStart(log=self.log) + starter.initialize(['--profile-dir', profile_dir]) + cl = starter.controller_launcher + esl = starter.engine_launcher + n = starter.n + return cl, esl, n + + def get_profile_dir(self, name, path): + p = ProfileDir.find_profile_dir_by_name(path,name=name) + return p.location + + def update_profiles(self): + """List all profiles in the ipython_dir and cwd. + """ + for path in [get_ipython_dir(), os.getcwdu()]: + for profile in list_profiles_in(path): + pd = self.get_profile_dir(profile, path) + if profile not in self.profiles: + self.log.debug("Overwriting profile %s" % profile) + self.profiles[profile] = { + 'profile': profile, + 'profile_dir': pd, + 'status': 'stopped' + } + + def list_profiles(self): + self.update_profiles() + result = [self.profile_info(p) for p in self.profiles.keys()] + result.sort() + return result + + def check_profile(self, profile): + if profile not in self.profiles: + raise web.HTTPError(404, u'profile not found') + + def profile_info(self, profile): + self.check_profile(profile) + result = {} + data = self.profiles.get(profile) + result['profile'] = profile + result['profile_dir'] = data['profile_dir'] + result['status'] = data['status'] + if 'n' in data: + result['n'] = data['n'] + return result + + def start_cluster(self, profile, n=None): + """Start a cluster for a given profile.""" + self.check_profile(profile) + data = self.profiles[profile] + if data['status'] == 'running': + raise web.HTTPError(409, u'cluster already running') + cl, esl, default_n = self.build_launchers(data['profile_dir']) + n = n if n is not None else default_n + def clean_data(): + data.pop('controller_launcher',None) + data.pop('engine_set_launcher',None) + data.pop('n',None) + data['status'] = 'stopped' + def engines_stopped(r): + self.log.debug('Engines stopped') + if cl.running: + cl.stop() + clean_data() + esl.on_stop(engines_stopped) + def controller_stopped(r): + self.log.debug('Controller stopped') + if esl.running: + esl.stop() + clean_data() + cl.on_stop(controller_stopped) + + dc = ioloop.DelayedCallback(lambda: cl.start(), 0, self.loop) + dc.start() + dc = ioloop.DelayedCallback(lambda: esl.start(n), 1000*self.delay, self.loop) + dc.start() + + self.log.debug('Cluster started') + data['controller_launcher'] = cl + data['engine_set_launcher'] = esl + data['n'] = n + data['status'] = 'running' + return self.profile_info(profile) + + def stop_cluster(self, profile): + """Stop a cluster for a given profile.""" + self.check_profile(profile) + data = self.profiles[profile] + if data['status'] == 'stopped': + raise web.HTTPError(409, u'cluster not running') + data = self.profiles[profile] + cl = data['controller_launcher'] + esl = data['engine_set_launcher'] + if cl.running: + cl.stop() + if esl.running: + esl.stop() + # Return a temp info dict, the real one is updated in the on_stop + # logic above. + result = { + 'profile': data['profile'], + 'profile_dir': data['profile_dir'], + 'status': 'stopped' + } + return result + + def stop_all_clusters(self): + for p in self.profiles.keys(): + self.stop_cluster(profile) diff --git a/IPython/frontend/html/notebook/handlers.py b/IPython/frontend/html/notebook/handlers.py index faba77d..62b3b82 100644 --- a/IPython/frontend/html/notebook/handlers.py +++ b/IPython/frontend/html/notebook/handlers.py @@ -211,6 +211,7 @@ class LoginHandler(AuthenticatedHandler): read_only=self.read_only, logged_in=self.logged_in, login_available=self.login_available, + base_project_url=self.application.ipython_app.base_project_url, message=message ) @@ -246,6 +247,7 @@ class LogoutHandler(AuthenticatedHandler): read_only=self.read_only, logged_in=self.logged_in, login_available=self.login_available, + base_project_url=self.application.ipython_app.base_project_url, message=message) @@ -587,7 +589,6 @@ class NotebookRootHandler(AuthenticatedHandler): @authenticate_unless_readonly def get(self): - nbm = self.application.notebook_manager files = nbm.list_notebooks() self.finish(jsonapi.dumps(files)) @@ -661,6 +662,44 @@ class NotebookCopyHandler(AuthenticatedHandler): mathjax_url=self.application.ipython_app.mathjax_url, ) + +#----------------------------------------------------------------------------- +# Cluster handlers +#----------------------------------------------------------------------------- + + +class MainClusterHandler(AuthenticatedHandler): + + @web.authenticated + def get(self): + cm = self.application.cluster_manager + self.finish(jsonapi.dumps(cm.list_profiles())) + + +class ClusterProfileHandler(AuthenticatedHandler): + + @web.authenticated + def get(self, profile): + cm = self.application.cluster_manager + self.finish(jsonapi.dumps(cm.profile_info(profile))) + + +class ClusterActionHandler(AuthenticatedHandler): + + @web.authenticated + def post(self, profile, action): + cm = self.application.cluster_manager + if action == 'start': + n = self.get_argument('n',default=None) + if n is None: + data = cm.start_cluster(profile) + else: + data = cm.start_cluster(profile,int(n)) + if action == 'stop': + data = cm.stop_cluster(profile) + self.finish(jsonapi.dumps(data)) + + #----------------------------------------------------------------------------- # RST web service handlers #----------------------------------------------------------------------------- diff --git a/IPython/frontend/html/notebook/notebookapp.py b/IPython/frontend/html/notebook/notebookapp.py index 09b91de..8f4f10a 100644 --- a/IPython/frontend/html/notebook/notebookapp.py +++ b/IPython/frontend/html/notebook/notebookapp.py @@ -49,9 +49,11 @@ from .handlers import (LoginHandler, LogoutHandler, ProjectDashboardHandler, NewHandler, NamedNotebookHandler, MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler, ShellHandler, NotebookRootHandler, NotebookHandler, NotebookCopyHandler, - RSTHandler, AuthenticatedFileHandler, PrintNotebookHandler + RSTHandler, AuthenticatedFileHandler, PrintNotebookHandler, + MainClusterHandler, ClusterProfileHandler, ClusterActionHandler ) from .notebookmanager import NotebookManager +from .clustermanager import ClusterManager from IPython.config.application import catch_config_error, boolean_flag from IPython.core.application import BaseIPythonApplication @@ -74,6 +76,9 @@ from IPython.utils import py3compat _kernel_id_regex = r"(?P\w+-\w+-\w+-\w+-\w+)" _kernel_action_regex = r"(?Prestart|interrupt)" _notebook_id_regex = r"(?P\w+-\w+-\w+-\w+-\w+)" +_profile_regex = r"(?P[a-zA-Z0-9]+)" +_cluster_action_regex = r"(?Pstart|stop)" + LOCALHOST = '127.0.0.1' @@ -101,7 +106,8 @@ def url_path_join(a,b): class NotebookWebApplication(web.Application): - def __init__(self, ipython_app, kernel_manager, notebook_manager, log, + def __init__(self, ipython_app, kernel_manager, notebook_manager, + cluster_manager, log, base_project_url, settings_overrides): handlers = [ (r"/", ProjectDashboardHandler), @@ -120,6 +126,9 @@ class NotebookWebApplication(web.Application): (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler), (r"/rstservice/render", RSTHandler), (r"/files/(.*)", AuthenticatedFileHandler, {'path' : notebook_manager.notebook_dir}), + (r"/clusters", MainClusterHandler), + (r"/clusters/%s/%s" % (_profile_regex, _cluster_action_regex), ClusterActionHandler), + (r"/clusters/%s" % _profile_regex, ClusterProfileHandler), ] settings = dict( template_path=os.path.join(os.path.dirname(__file__), "templates"), @@ -151,10 +160,11 @@ class NotebookWebApplication(web.Application): super(NotebookWebApplication, self).__init__(new_handlers, **settings) self.kernel_manager = kernel_manager - self.log = log self.notebook_manager = notebook_manager + self.cluster_manager = cluster_manager self.ipython_app = ipython_app self.read_only = self.ipython_app.read_only + self.log = log #----------------------------------------------------------------------------- @@ -395,6 +405,8 @@ class NotebookApp(BaseIPythonApplication): ) self.notebook_manager = NotebookManager(config=self.config, log=self.log) self.notebook_manager.list_notebooks() + self.cluster_manager = ClusterManager(config=self.config, log=self.log) + self.cluster_manager.update_profiles() def init_logging(self): super(NotebookApp, self).init_logging() @@ -406,7 +418,8 @@ class NotebookApp(BaseIPythonApplication): def init_webapp(self): """initialize tornado webapp and httpserver""" self.web_app = NotebookWebApplication( - self, self.kernel_manager, self.notebook_manager, self.log, + self, self.kernel_manager, self.notebook_manager, + self.cluster_manager, self.log, self.base_project_url, self.webapp_settings ) if self.certfile: diff --git a/IPython/frontend/html/notebook/static/css/layout.css b/IPython/frontend/html/notebook/static/css/fbm.css similarity index 85% rename from IPython/frontend/html/notebook/static/css/layout.css rename to IPython/frontend/html/notebook/static/css/fbm.css index d8bec47..0e21d19 100644 --- a/IPython/frontend/html/notebook/static/css/layout.css +++ b/IPython/frontend/html/notebook/static/css/fbm.css @@ -1,10 +1,4 @@ -.border-box-sizing { - box-sizing: border-box; - -moz-box-sizing: border-box; - -webkit-box-sizing: border-box; -} - /* Flexible box model classes */ /* Taken from Alex Russell http://infrequently.org/2009/08/css-3-progress/ */ @@ -101,30 +95,3 @@ -moz-box-pack: center; box-pack: center; } - -.message { - border-width: 1px; - border-style: solid; - text-align: center; - padding: 0.5em; - margin: 0.5em 0; -} - -.message.error { - background-color: #FFD3D1; - border-color: red; -} - -.message.warning { - background-color: #FFD09E; - border-color: orange; -} - -.message.info { - background-color: #CBFFBA; - border-color: green; -} - -#content_panel { - margin: 0.5em; -} \ No newline at end of file diff --git a/IPython/frontend/html/notebook/static/css/login.css b/IPython/frontend/html/notebook/static/css/login.css new file mode 100644 index 0000000..41eb790 --- /dev/null +++ b/IPython/frontend/html/notebook/static/css/login.css @@ -0,0 +1,6 @@ + +#main_app { + height: 100px; + width: 350px; + margin: 50px auto; +} diff --git a/IPython/frontend/html/notebook/static/css/logout.css b/IPython/frontend/html/notebook/static/css/logout.css new file mode 100644 index 0000000..7767494 --- /dev/null +++ b/IPython/frontend/html/notebook/static/css/logout.css @@ -0,0 +1,7 @@ + +#main_app { + height: 100px; + width: 200px; + margin: 50px auto; +} + diff --git a/IPython/frontend/html/notebook/static/css/notebook.css b/IPython/frontend/html/notebook/static/css/notebook.css index 6fa3ab9..88f9259 100644 --- a/IPython/frontend/html/notebook/static/css/notebook.css +++ b/IPython/frontend/html/notebook/static/css/notebook.css @@ -6,14 +6,6 @@ body { - background-color: white; - /* This makes sure that the body covers the entire window and needs to - be in a different element than the display: box in wrapper below */ - position: absolute; - left: 0px; - right: 0px; - top: 0px; - bottom: 0px; overflow: hidden; } @@ -31,11 +23,6 @@ span#notebook_name { font-size: 146.5%; } -#menubar { - /* Initially hidden to prevent FLOUC */ - display: none; -} - .ui-menubar-item .ui-button .ui-button-text { padding: 0.4em 1.0em; font-size: 100%; @@ -69,8 +56,6 @@ span#notebook_name { } #toolbar { - /* Initially hidden to prevent FLOUC */ - display: none; padding: 3px 15px; } @@ -78,6 +63,12 @@ span#notebook_name { font-size: 85%; } + +div#main_app { + width: 100%; + position: relative; +} + span#quick_help_area { position: static; padding: 5px 0px; diff --git a/IPython/frontend/html/notebook/static/css/base.css b/IPython/frontend/html/notebook/static/css/page.css similarity index 87% rename from IPython/frontend/html/notebook/static/css/base.css rename to IPython/frontend/html/notebook/static/css/page.css index 5088bfb..ff2b16c 100644 --- a/IPython/frontend/html/notebook/static/css/base.css +++ b/IPython/frontend/html/notebook/static/css/page.css @@ -14,7 +14,7 @@ body { right: 0px; top: 0px; bottom: 0px; - overflow: hidden; + overflow: visible; } @@ -41,11 +41,9 @@ span#ipython_notebook h1 img { color: black; } -div#main_app { - /* Initially hidden to prevent FLOUC */ - display: none; +#site { width: 100%; - position: relative; + display: none; } /* We set the fonts by hand here to override the values in the theme */ @@ -63,11 +61,17 @@ div#main_app { font-size: 77%; } +input.ui-button { + padding: 0.3em 0.9em; +} + span#login_widget { float: right; } -/* generic class for hidden objects */ -.hidden { - display: none; +.border-box-sizing { + box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; } + diff --git a/IPython/frontend/html/notebook/static/css/projectdashboard.css b/IPython/frontend/html/notebook/static/css/projectdashboard.css index 0ca7484..640fb84 100644 --- a/IPython/frontend/html/notebook/static/css/projectdashboard.css +++ b/IPython/frontend/html/notebook/static/css/projectdashboard.css @@ -5,70 +5,68 @@ * Author: IPython Development Team */ - -body { - background-color: white; - /* This makes sure that the body covers the entire window and needs to - be in a different element than the display: box in wrapper below */ - position: absolute; - left: 0px; - right: 0px; - top: 0px; - bottom: 0px; - overflow: auto; -} - -#left_panel { +#main_app { + width: 920px; + margin: 30px auto 0px auto; } -#drop_zone { - height: 200px; - width: 200px +#tabs { + border-style: none; } -#content_panel { - width: 600px; +#tab1, #tab2 { + padding: 1em 0em; } -#content_toolbar { +.list_toolbar { padding: 5px; height: 25px; line-height: 25px; } -#header_border { - width: 100%; - height: 2px; -} - -#app_hbox { - width: 100%; -} - -#drag_info { +.toolbar_info { float: left; } -#notebooks_buttons { +.toolbar_buttons { float: right; } -#project_name { +.list_header { height: 25px; line-height: 25px; - padding: 3px; + padding: 3px 5px; } -.notebook_item { + + +.list_item { height: 25px; line-height: 25px; - padding: 3px; + padding: 3px 5px; } .notebook_item a { text-decoration: none; } +.profile_col { +} + +.status_col { + float: right; + width: 325px; +} + +.engines_col { + float: right; + width: 325px; +} + +.action_col { + float: right; +} + .item_buttons { float: right; } @@ -80,3 +78,7 @@ body { .highlight_text { color: blue; } + +.ui-tabs .ui-tabs-nav li a { + padding: .3em .5em; +} diff --git a/IPython/frontend/html/notebook/static/js/clusterlist.js b/IPython/frontend/html/notebook/static/js/clusterlist.js new file mode 100644 index 0000000..8cd5d11 --- /dev/null +++ b/IPython/frontend/html/notebook/static/js/clusterlist.js @@ -0,0 +1,180 @@ +//---------------------------------------------------------------------------- +// Copyright (C) 2008-2011 The IPython Development Team +// +// Distributed under the terms of the BSD License. The full license is in +// the file COPYING, distributed as part of this software. +//---------------------------------------------------------------------------- + +//============================================================================ +// NotebookList +//============================================================================ + +var IPython = (function (IPython) { + + var ClusterList = function (selector) { + this.selector = selector; + if (this.selector !== undefined) { + this.element = $(selector); + this.style(); + this.bind_events(); + } + }; + + ClusterList.prototype.style = function () { + $('#cluster_toolbar').addClass('list_toolbar'); + $('#cluster_list_info').addClass('toolbar_info'); + $('#cluster_buttons').addClass('toolbar_buttons'); + $('div#cluster_header').addClass('list_header ui-widget ui-widget-header ui-helper-clearfix'); + $('div#cluster_header').children().eq(0).addClass('profile_col'); + $('div#cluster_header').children().eq(1).addClass('action_col'); + $('div#cluster_header').children().eq(2).addClass('engines_col'); + $('div#cluster_header').children().eq(3).addClass('status_col'); + $('#refresh_cluster_list').button({ + icons : {primary: 'ui-icon-arrowrefresh-1-s'}, + text : false + }); + }; + + + ClusterList.prototype.bind_events = function () { + var that = this; + $('#refresh_cluster_list').click(function () { + that.load_list(); + }); + }; + + + ClusterList.prototype.load_list = function () { + var settings = { + processData : false, + cache : false, + type : "GET", + dataType : "json", + success : $.proxy(this.load_list_success, this) + }; + var url = $('body').data('baseProjectUrl') + 'clusters'; + $.ajax(url, settings); + }; + + + ClusterList.prototype.clear_list = function () { + this.element.children('.list_item').remove(); + } + + ClusterList.prototype.load_list_success = function (data, status, xhr) { + this.clear_list(); + var len = data.length; + for (var i=0; i'); + var item = new ClusterItem(item_div); + item.update_state(data[i]); + item_div.data('item', item); + this.element.append(item_div); + }; + }; + + + var ClusterItem = function (element) { + this.element = $(element); + this.data = null; + this.style(); + }; + + + ClusterItem.prototype.style = function () { + this.element.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix'); + this.element.css('border-top-style','none'); + } + + ClusterItem.prototype.update_state = function (data) { + this.data = data; + if (data.status === 'running') { + this.state_running(); + } else if (data.status === 'stopped') { + this.state_stopped(); + }; + + } + + + ClusterItem.prototype.state_stopped = function () { + var that = this; + this.element.empty(); + var profile_col = $('').addClass('profile_col').text(this.data.profile); + var status_col = $('').addClass('status_col').html('stopped'); + var engines_col = $('').addClass('engines_col'); + var input = $('').attr('type','text'). + attr('size',3).addClass('engine_num_input'); + engines_col.append(input); + var action_col = $('').addClass('action_col'); + var start_button = $('').button(); + action_col.append(start_button); + this.element.append(profile_col). + append(action_col). + append(engines_col). + append(status_col); + start_button.click(function (e) { + var n = that.element.find('.engine_num_input').val(); + if (!/^\d+$/.test(n) && n.length>0) { + status_col.html('invalid engine #'); + } else { + var settings = { + cache : false, + data : {n:n}, + type : "POST", + dataType : "json", + success : function (data, status, xhr) { + that.update_state(data); + }, + error : function (data, status, xhr) { + status_col.html("error starting cluster") + } + }; + status_col.html('starting'); + var url = $('body').data('baseProjectUrl') + 'clusters/' + that.data.profile + '/start'; + $.ajax(url, settings); + }; + }); + }; + + + ClusterItem.prototype.state_running = function () { + this.element.empty(); + var that = this; + var profile_col = $('').addClass('profile_col').text(this.data.profile); + var status_col = $('').addClass('status_col').html('running'); + var engines_col = $('').addClass('engines_col').html(this.data.n); + var action_col = $('').addClass('action_col'); + var stop_button = $('').button(); + action_col.append(stop_button); + this.element.append(profile_col). + append(action_col). + append(engines_col). + append(status_col); + stop_button.click(function (e) { + var settings = { + cache : false, + type : "POST", + dataType : "json", + success : function (data, status, xhr) { + that.update_state(data); + }, + error : function (data, status, xhr) { + console.log('error',data); + status_col.html("error stopping cluster") + } + }; + status_col.html('stopping') + var url = $('body').data('baseProjectUrl') + 'clusters/' + that.data.profile + '/stop'; + $.ajax(url, settings); + }); + }; + + + IPython.ClusterList = ClusterList; + IPython.ClusterItem = ClusterItem; + + return IPython; + +}(IPython)); + diff --git a/IPython/frontend/html/notebook/static/js/initmathjax.js b/IPython/frontend/html/notebook/static/js/initmathjax.js new file mode 100644 index 0000000..5987eaa --- /dev/null +++ b/IPython/frontend/html/notebook/static/js/initmathjax.js @@ -0,0 +1,83 @@ +//---------------------------------------------------------------------------- +// Copyright (C) 2008-2011 The IPython Development Team +// +// Distributed under the terms of the BSD License. The full license is in +// the file COPYING, distributed as part of this software. +//---------------------------------------------------------------------------- + +//============================================================================ +// MathJax initialization +//============================================================================ + +var IPython = (function (IPython) { + + var init_mathjax = function () { + if (window.MathJax) { + // MathJax loaded + MathJax.Hub.Config({ + tex2jax: { + inlineMath: [ ['$','$'], ["\\(","\\)"] ], + displayMath: [ ['$$','$$'], ["\\[","\\]"] ] + }, + displayAlign: 'left', // Change this to 'center' to center equations. + "HTML-CSS": { + styles: {'.MathJax_Display': {"margin": 0}} + } + }); + } else if (window.mathjax_url != "") { + // Don't have MathJax, but should. Show dialog. + var dialog = $('
') + .append( + $("

").addClass('dialog').html( + "Math/LaTeX rendering will be disabled." + ) + ).append( + $("

").addClass('dialog').html( + "If you have administrative access to the notebook server and" + + " a working internet connection, you can install a local copy" + + " of MathJax for offline use with the following command on the server" + + " at a Python or IPython prompt:" + ) + ).append( + $("
").addClass('dialog').html(
+                        ">>> from IPython.external import mathjax; mathjax.install_mathjax()"
+                    )
+                ).append(
+                    $("

").addClass('dialog').html( + "This will try to install MathJax into the IPython source directory." + ) + ).append( + $("

").addClass('dialog').html( + "If IPython is installed to a location that requires" + + " administrative privileges to write, you will need to make this call as" + + " an administrator, via 'sudo'." + ) + ).append( + $("

").addClass('dialog').html( + "When you start the notebook server, you can instruct it to disable MathJax support altogether:" + ) + ).append( + $("
").addClass('dialog').html(
+                        "$ ipython notebook --no-mathjax"
+                    )
+                ).append(
+                    $("

").addClass('dialog').html( + "which will prevent this dialog from appearing." + ) + ).dialog({ + title: "Failed to retrieve MathJax from '" + window.mathjax_url + "'", + width: "70%", + modal: true, + }) + } else { + // No MathJax, but none expected. No dialog. + }; + }; + + + // Set module variables + IPython.init_mathjax = init_mathjax; + + return IPython; + +}(IPython)); \ No newline at end of file diff --git a/IPython/frontend/html/notebook/static/js/layout.js b/IPython/frontend/html/notebook/static/js/layoutmanager.js similarity index 93% rename from IPython/frontend/html/notebook/static/js/layout.js rename to IPython/frontend/html/notebook/static/js/layoutmanager.js index e6f27b8..d596401 100644 --- a/IPython/frontend/html/notebook/static/js/layout.js +++ b/IPython/frontend/html/notebook/static/js/layoutmanager.js @@ -38,9 +38,9 @@ var IPython = (function (IPython) { } else { toolbar_height = $('div#toolbar').outerHeight(true); } - var app_height = h-header_height-menubar_height-toolbar_height-2; // content height + var app_height = h-header_height-menubar_height-toolbar_height; // content height - $('div#main_app').height(app_height + 2); // content+padding+border height + $('div#main_app').height(app_height); // content+padding+border height var pager_height = IPython.pager.percentage_height*app_height; var pager_splitter_height = $('div#pager_splitter').outerHeight(true); diff --git a/IPython/frontend/html/notebook/static/js/loginmain.js b/IPython/frontend/html/notebook/static/js/loginmain.js index 684d3e0..503f1ea 100644 --- a/IPython/frontend/html/notebook/static/js/loginmain.js +++ b/IPython/frontend/html/notebook/static/js/loginmain.js @@ -12,19 +12,11 @@ $(document).ready(function () { - $('div#header').addClass('border-box-sizing'); - $('div#header_border').addClass('border-box-sizing ui-widget ui-widget-content'); - + IPython.page = new IPython.Page(); + $('input#login_submit').button(); $('div#main_app').addClass('border-box-sizing ui-widget'); - $('div#app_hbox').addClass('hbox'); - - $('div#left_panel').addClass('box-flex'); - $('div#right_panel').addClass('box-flex'); - $('input#signin').button(); - - // These have display: none in the css file and are made visible here to prevent FLOUC. - $('div#header').css('display','block'); - $('div#main_app').css('display','block'); + IPython.page.show(); + $('input#password_input').focus(); }); diff --git a/IPython/frontend/html/notebook/static/js/loginwidget.js b/IPython/frontend/html/notebook/static/js/loginwidget.js index 763118e..288772a 100644 --- a/IPython/frontend/html/notebook/static/js/loginwidget.js +++ b/IPython/frontend/html/notebook/static/js/loginwidget.js @@ -24,6 +24,8 @@ var IPython = (function (IPython) { this.element.find('button#logout').button(); this.element.find('button#login').button(); }; + + LoginWidget.prototype.bind_events = function () { var that = this; this.element.find("button#logout").click(function () { diff --git a/IPython/frontend/html/notebook/static/js/logoutmain.js b/IPython/frontend/html/notebook/static/js/logoutmain.js new file mode 100644 index 0000000..8bbddb4 --- /dev/null +++ b/IPython/frontend/html/notebook/static/js/logoutmain.js @@ -0,0 +1,20 @@ +//---------------------------------------------------------------------------- +// Copyright (C) 2008-2011 The IPython Development Team +// +// Distributed under the terms of the BSD License. The full license is in +// the file COPYING, distributed as part of this software. +//---------------------------------------------------------------------------- + +//============================================================================ +// On document ready +//============================================================================ + + +$(document).ready(function () { + + IPython.page = new IPython.Page(); + $('div#main_app').addClass('border-box-sizing ui-widget'); + IPython.page.show(); + +}); + diff --git a/IPython/frontend/html/notebook/static/js/menubar.js b/IPython/frontend/html/notebook/static/js/menubar.js index eef2e44..a3d3ec0 100644 --- a/IPython/frontend/html/notebook/static/js/menubar.js +++ b/IPython/frontend/html/notebook/static/js/menubar.js @@ -22,6 +22,7 @@ var IPython = (function (IPython) { MenuBar.prototype.style = function () { + this.element.addClass('border-box-sizing'); $('ul#menus').menubar({ select : function (event, ui) { // The selected cell loses focus when the menu is entered, so we diff --git a/IPython/frontend/html/notebook/static/js/notebooklist.js b/IPython/frontend/html/notebook/static/js/notebooklist.js index 50dd96a..a7e98ee 100644 --- a/IPython/frontend/html/notebook/static/js/notebooklist.js +++ b/IPython/frontend/html/notebook/static/js/notebooklist.js @@ -21,8 +21,14 @@ var IPython = (function (IPython) { }; NotebookList.prototype.style = function () { - this.element.addClass('ui-widget ui-widget-content'); - $('div#project_name').addClass('ui-widget ui-widget-header'); + $('#notebook_toolbar').addClass('list_toolbar'); + $('#drag_info').addClass('toolbar_info'); + $('#notebook_buttons').addClass('toolbar_buttons'); + $('div#project_name').addClass('list_header ui-widget ui-widget-header'); + $('#refresh_notebook_list').button({ + icons : {primary: 'ui-icon-arrowrefresh-1-s'}, + text : false + }); }; @@ -31,6 +37,9 @@ var IPython = (function (IPython) { return; } var that = this; + $('#refresh_notebook_list').click(function () { + that.load_list(); + }); this.element.bind('dragover', function () { return false; }); @@ -62,7 +71,13 @@ var IPython = (function (IPython) { }; + NotebookList.prototype.clear_list = function () { + this.element.children('.list_item').remove(); + } + + NotebookList.prototype.load_list = function () { + this.clear_list(); var settings = { processData : false, cache : false, @@ -93,7 +108,8 @@ var IPython = (function (IPython) { NotebookList.prototype.new_notebook_item = function (index) { var item = $('
'); - item.addClass('notebook_item ui-widget ui-widget-content ui-helper-clearfix'); + item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix'); + item.css('border-top-style','none'); var item_name = $('').addClass('item_name'); item.append(item_name); @@ -156,7 +172,7 @@ var IPython = (function (IPython) { var that = $(this); // We use the nbname and notebook_id from the parent notebook_item element's // data because the outer scopes values change as we iterate through the loop. - var parent_item = that.parents('div.notebook_item'); + var parent_item = that.parents('div.list_item'); var nbname = parent_item.data('nbname'); var notebook_id = parent_item.data('notebook_id'); var dialog = $('
'); diff --git a/IPython/frontend/html/notebook/static/js/notebookmain.js b/IPython/frontend/html/notebook/static/js/notebookmain.js index 34cd61c..6a613ef 100644 --- a/IPython/frontend/html/notebook/static/js/notebookmain.js +++ b/IPython/frontend/html/notebook/static/js/notebookmain.js @@ -11,75 +11,17 @@ $(document).ready(function () { - if (window.MathJax){ - // MathJax loaded - MathJax.Hub.Config({ - tex2jax: { - inlineMath: [ ['$','$'], ["\\(","\\)"] ], - displayMath: [ ['$$','$$'], ["\\[","\\]"] ] - }, - displayAlign: 'left', // Change this to 'center' to center equations. - "HTML-CSS": { - styles: {'.MathJax_Display': {"margin": 0}} - } - }); - }else if (window.mathjax_url != ""){ - // Don't have MathJax, but should. Show dialog. - var dialog = $('
') - .append( - $("

").addClass('dialog').html( - "Math/LaTeX rendering will be disabled." - ) - ).append( - $("

").addClass('dialog').html( - "If you have administrative access to the notebook server and" + - " a working internet connection, you can install a local copy" + - " of MathJax for offline use with the following command on the server" + - " at a Python or IPython prompt:" - ) - ).append( - $("
").addClass('dialog').html(
-                    ">>> from IPython.external import mathjax; mathjax.install_mathjax()"
-                )
-            ).append(
-                $("

").addClass('dialog').html( - "This will try to install MathJax into the IPython source directory." - ) - ).append( - $("

").addClass('dialog').html( - "If IPython is installed to a location that requires" + - " administrative privileges to write, you will need to make this call as" + - " an administrator, via 'sudo'." - ) - ).append( - $("

").addClass('dialog').html( - "When you start the notebook server, you can instruct it to disable MathJax support altogether:" - ) - ).append( - $("
").addClass('dialog').html(
-                    "$ ipython notebook --no-mathjax"
-                )
-            ).append(
-                $("

").addClass('dialog').html( - "which will prevent this dialog from appearing." - ) - ).dialog({ - title: "Failed to retrieve MathJax from '" + window.mathjax_url + "'", - width: "70%", - modal: true, - }) - }else{ - // No MathJax, but none expected. No dialog. - } - - IPython.markdown_converter = new Markdown.Converter(); - IPython.read_only = $('meta[name=read_only]').attr("content") == 'True'; + IPython.init_mathjax(); - $('div#header').addClass('border-box-sizing'); - $('div#main_app').addClass('border-box-sizing ui-widget ui-widget-content'); + IPython.read_only = $('body').data('readOnly') === 'True'; + $('div#main_app').addClass('border-box-sizing ui-widget'); $('div#notebook_panel').addClass('border-box-sizing ui-widget'); + // The header's bottom border is provided by the menu bar so we remove it. + $('div#header').css('border-bottom-style','none'); + IPython.page = new IPython.Page(); + IPython.markdown_converter = new Markdown.Converter(); IPython.layout_manager = new IPython.LayoutManager(); IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter'); IPython.quick_help = new IPython.QuickHelp('span#quick_help_area'); @@ -92,22 +34,16 @@ $(document).ready(function () { IPython.layout_manager.do_resize(); - // These have display: none in the css file and are made visible here to prevent FLOUC. - $('div#header').css('display','block'); - if(IPython.read_only){ // hide various elements from read-only view $('div#pager').remove(); $('div#pager_splitter').remove(); - $('span#login_widget').removeClass('hidden'); // set the notebook name field as not modifiable $('#notebook_name').attr('disabled','disabled') } - $('div#menubar').css('display','block'); - $('div#toolbar').css('display','block'); - $('div#main_app').css('display','block'); + IPython.page.show(); IPython.layout_manager.do_resize(); $([IPython.events]).on('notebook_loaded.Notebook', function () { diff --git a/IPython/frontend/html/notebook/static/js/page.js b/IPython/frontend/html/notebook/static/js/page.js new file mode 100644 index 0000000..6a58a71 --- /dev/null +++ b/IPython/frontend/html/notebook/static/js/page.js @@ -0,0 +1,59 @@ +//---------------------------------------------------------------------------- +// Copyright (C) 2008-2011 The IPython Development Team +// +// Distributed under the terms of the BSD License. The full license is in +// the file COPYING, distributed as part of this software. +//---------------------------------------------------------------------------- + +//============================================================================ +// Global header/site setup. +//============================================================================ + +var IPython = (function (IPython) { + + var Page = function () { + this.style(); + this.bind_events(); + }; + + Page.prototype.style = function () { + $('div#header').addClass('border-box-sizing'). + addClass('ui-widget ui-widget-content'). + css('border-top-style','none'). + css('border-left-style','none'). + css('border-right-style','none'); + $('div#site').addClass('border-box-sizing') + }; + + + Page.prototype.bind_events = function () { + }; + + + Page.prototype.show = function () { + // The header and site divs start out hidden to prevent FLOUC. + // Main scripts should call this method after styling everything. + this.show_header(); + this.show_site(); + }; + + + Page.prototype.show_header = function () { + // The header and site divs start out hidden to prevent FLOUC. + // Main scripts should call this method after styling everything. + $('div#header').css('display','block'); + }; + + + Page.prototype.show_site = function () { + // The header and site divs start out hidden to prevent FLOUC. + // Main scripts should call this method after styling everything. + $('div#site').css('display','block'); + }; + + + IPython.Page = Page; + + return IPython; + +}(IPython)); diff --git a/IPython/frontend/html/notebook/static/js/pagemain.js b/IPython/frontend/html/notebook/static/js/pagemain.js new file mode 100644 index 0000000..35c2670 --- /dev/null +++ b/IPython/frontend/html/notebook/static/js/pagemain.js @@ -0,0 +1,19 @@ +//---------------------------------------------------------------------------- +// Copyright (C) 2008-2011 The IPython Development Team +// +// Distributed under the terms of the BSD License. The full license is in +// the file COPYING, distributed as part of this software. +//---------------------------------------------------------------------------- + +//============================================================================ +// On document ready +//============================================================================ + + +$(document).ready(function () { + + IPython.page = new IPython.Page(); + IPython.page.show(); + +}); + diff --git a/IPython/frontend/html/notebook/static/js/printnotebookmain.js b/IPython/frontend/html/notebook/static/js/printnotebookmain.js index 55f168c..ba773cc 100644 --- a/IPython/frontend/html/notebook/static/js/printnotebookmain.js +++ b/IPython/frontend/html/notebook/static/js/printnotebookmain.js @@ -11,81 +11,18 @@ $(document).ready(function () { - if (window.MathJax){ - // MathJax loaded - MathJax.Hub.Config({ - tex2jax: { - inlineMath: [ ['$','$'], ["\\(","\\)"] ], - displayMath: [ ['$$','$$'], ["\\[","\\]"] ] - }, - displayAlign: 'left', // Change this to 'center' to center equations. - "HTML-CSS": { - styles: {'.MathJax_Display': {"margin": 0}} - } - }); - }else if (window.mathjax_url != ""){ - // Don't have MathJax, but should. Show dialog. - var dialog = $('
') - .append( - $("

").addClass('dialog').html( - "Math/LaTeX rendering will be disabled." - ) - ).append( - $("

").addClass('dialog').html( - "If you have administrative access to the notebook server and" + - " a working internet connection, you can install a local copy" + - " of MathJax for offline use with the following command on the server" + - " at a Python or IPython prompt:" - ) - ).append( - $("
").addClass('dialog').html(
-                    ">>> from IPython.external import mathjax; mathjax.install_mathjax()"
-                )
-            ).append(
-                $("

").addClass('dialog').html( - "This will try to install MathJax into the IPython source directory." - ) - ).append( - $("

").addClass('dialog').html( - "If IPython is installed to a location that requires" + - " administrative privileges to write, you will need to make this call as" + - " an administrator, via 'sudo'." - ) - ).append( - $("

").addClass('dialog').html( - "When you start the notebook server, you can instruct it to disable MathJax support altogether:" - ) - ).append( - $("
").addClass('dialog').html(
-                    "$ ipython notebook --no-mathjax"
-                )
-            ).append(
-                $("

").addClass('dialog').html( - "which will prevent this dialog from appearing." - ) - ).dialog({ - title: "Failed to retrieve MathJax from '" + window.mathjax_url + "'", - width: "70%", - modal: true, - }) - }else{ - // No MathJax, but none expected. No dialog. - } - - IPython.markdown_converter = new Markdown.Converter(); - IPython.read_only = $('meta[name=read_only]').attr("content") == 'True'; - $('div#header').addClass('border-box-sizing'); - $('div#main_app').addClass('border-box-sizing ui-widget ui-widget-content'); + IPython.init_mathjax(); + + IPython.read_only = $('body').data('readOnly') === 'True'; + $('div#main_app').addClass('border-box-sizing ui-widget'); $('div#notebook_panel').addClass('border-box-sizing ui-widget'); + IPython.page = new IPython.Page(); + IPython.markdown_converter = new Markdown.Converter(); IPython.login_widget = new IPython.LoginWidget('span#login_widget'); IPython.notebook = new IPython.Notebook('div#notebook'); - IPython.save_widget = new IPython.SaveWidget('span#save_widget'); - - // These have display: none in the css file and are made visible here to prevent FLOUC. - $('div#header').css('display','block'); - $('div#main_app').css('display','block'); + IPython.page.show_site(); IPython.notebook.load_notebook($('body').data('notebookId')); diff --git a/IPython/frontend/html/notebook/static/js/projectdashboardmain.js b/IPython/frontend/html/notebook/static/js/projectdashboardmain.js index 516b406..1f6a69f 100644 --- a/IPython/frontend/html/notebook/static/js/projectdashboardmain.js +++ b/IPython/frontend/html/notebook/static/js/projectdashboardmain.js @@ -12,31 +12,28 @@ $(document).ready(function () { - $('div#header').addClass('border-box-sizing'); - $('div#header_border').addClass('border-box-sizing ui-widget ui-widget-content'); + IPython.page = new IPython.Page(); + $('div#tabs').tabs(); + $('div#tabs').on('tabsselect', function (event, ui) { + var new_url = $('body').data('baseProjectUrl') + '#' + ui.panel.id; + window.history.replaceState({}, '', new_url); + }); $('div#main_app').addClass('border-box-sizing ui-widget'); - $('div#app_hbox').addClass('hbox'); - - $('div#content_toolbar').addClass('ui-widget ui-helper-clearfix'); - + $('div#notebooks_toolbar').addClass('ui-widget ui-helper-clearfix'); $('#new_notebook').button().click(function (e) { window.open($('body').data('baseProjectUrl')+'new'); }); - $('div#left_panel').addClass('box-flex'); - $('div#right_panel').addClass('box-flex'); - - IPython.read_only = $('meta[name=read_only]').attr("content") == 'True'; + IPython.read_only = $('body').data('readOnly') === 'True'; IPython.notebook_list = new IPython.NotebookList('div#notebook_list'); + IPython.cluster_list = new IPython.ClusterList('div#cluster_list'); IPython.login_widget = new IPython.LoginWidget('span#login_widget'); IPython.notebook_list.load_list(); + IPython.cluster_list.load_list(); - // These have display: none in the css file and are made visible here to prevent FLOUC. - $('div#header').css('display','block'); - $('div#main_app').css('display','block'); - + IPython.page.show(); }); diff --git a/IPython/frontend/html/notebook/static/js/toolbar.js b/IPython/frontend/html/notebook/static/js/toolbar.js index 78820c3..564ed0a 100644 --- a/IPython/frontend/html/notebook/static/js/toolbar.js +++ b/IPython/frontend/html/notebook/static/js/toolbar.js @@ -22,7 +22,11 @@ var IPython = (function (IPython) { ToolBar.prototype.style = function () { - this.element.addClass('border-box-sizing'); + this.element.addClass('border-box-sizing'). + addClass('ui-widget ui-widget-content'). + css('border-top-style','none'). + css('border-left-style','none'). + css('border-right-style','none'); this.element.find('#cell_type').addClass('ui-widget ui-widget-content'); this.element.find('#save_b').button({ icons : {primary: 'ui-icon-disk'}, diff --git a/IPython/frontend/html/notebook/templates/login.html b/IPython/frontend/html/notebook/templates/login.html index 4c5b2c0..b99dc08 100644 --- a/IPython/frontend/html/notebook/templates/login.html +++ b/IPython/frontend/html/notebook/templates/login.html @@ -1,26 +1,42 @@ -{% extends layout.html %} +{% extends page.html %} -{% block content_panel %} +{% block stylesheet %} - {% if login_available %} + + +{% end %} + + +{% block login_widget %} +{% end %} + + +{% block site %} +
+ + {% if login_available %}
- Password: - + Password: +
+ {% end %} + {% if message %} + {% for key in message %} +
+ {{message[key]}} +
+ {% end %} {% end %} -{% end %} +
-{% block login_widget %} {% end %} + {% block script %} - + + + {% end %} diff --git a/IPython/frontend/html/notebook/templates/logout.html b/IPython/frontend/html/notebook/templates/logout.html index 8087508..e31f0ac 100644 --- a/IPython/frontend/html/notebook/templates/logout.html +++ b/IPython/frontend/html/notebook/templates/logout.html @@ -1,28 +1,40 @@ -{% extends layout.html %} +{% extends page.html %} -{% block content_panel %} -
    - {% if read_only or not login_available %} +{% block stylesheet %} - Proceed to the list of notebooks. + - {% else %} +{% end %} + + +{% block login_widget %} +{% end %} + +{% block site %} - Proceed to the login page. +
    + {% if message %} + {% for key in message %} +
    + {{message[key]}} +
    + {% end %} {% end %} -
+ {% if read_only or not login_available %} + Proceed to the dashboard. + {% else %} + Proceed to the login page. + {% end %} -{% end %} -{% block login_widget %} +
+ {% end %} {% block script %} - + + + {% end %} diff --git a/IPython/frontend/html/notebook/templates/notebook.html b/IPython/frontend/html/notebook/templates/notebook.html index 7b0b087..97f384d 100644 --- a/IPython/frontend/html/notebook/templates/notebook.html +++ b/IPython/frontend/html/notebook/templates/notebook.html @@ -1,60 +1,49 @@ - - - - - - - IPython Notebook - - {% if mathjax_url %} - - {% end %} - - - - - - - - - - - - - - - {% comment In the notebook, the read-only flag is used to determine %} - {% comment whether to hide the side panels and switch off input %} - - - - - - - +{% block stylesheet %} + +{% if mathjax_url %} + +{% end %} + + + + + + + + + + +{% end %} + + +{% block params %} + +data-project={{project}} +data-base-project-url={{base_project_url}} +data-base-kernel-url={{base_kernel_url}} +data-read-only={{read_only and not logged_in}} +data-notebook-id={{notebook_id}} + +{% end %} + + +{% block header %} + + + + + + +{% end %} + + +{% block site %}