Show More
@@ -0,0 +1,175 b'' | |||||
|
1 | """Manage IPython.parallel clusters in the notebook. | |||
|
2 | ||||
|
3 | Authors: | |||
|
4 | ||||
|
5 | * Brian Granger | |||
|
6 | """ | |||
|
7 | ||||
|
8 | #----------------------------------------------------------------------------- | |||
|
9 | # Copyright (C) 2008-2011 The IPython Development Team | |||
|
10 | # | |||
|
11 | # Distributed under the terms of the BSD License. The full license is in | |||
|
12 | # the file COPYING, distributed as part of this software. | |||
|
13 | #----------------------------------------------------------------------------- | |||
|
14 | ||||
|
15 | #----------------------------------------------------------------------------- | |||
|
16 | # Imports | |||
|
17 | #----------------------------------------------------------------------------- | |||
|
18 | ||||
|
19 | import os | |||
|
20 | ||||
|
21 | from tornado import web | |||
|
22 | from zmq.eventloop import ioloop | |||
|
23 | ||||
|
24 | from IPython.config.configurable import LoggingConfigurable | |||
|
25 | from IPython.config.loader import load_pyconfig_files | |||
|
26 | from IPython.utils.traitlets import Dict, Instance, CFloat | |||
|
27 | from IPython.parallel.apps.ipclusterapp import IPClusterStart | |||
|
28 | from IPython.core.profileapp import list_profiles_in | |||
|
29 | from IPython.core.profiledir import ProfileDir | |||
|
30 | from IPython.utils.path import get_ipython_dir | |||
|
31 | from IPython.utils.sysinfo import num_cpus | |||
|
32 | ||||
|
33 | ||||
|
34 | #----------------------------------------------------------------------------- | |||
|
35 | # Classes | |||
|
36 | #----------------------------------------------------------------------------- | |||
|
37 | ||||
|
38 | ||||
|
39 | class DummyIPClusterStart(IPClusterStart): | |||
|
40 | """Dummy subclass to skip init steps that conflict with global app. | |||
|
41 | ||||
|
42 | Instantiating and initializing this class should result in fully configured | |||
|
43 | launchers, but no other side effects or state. | |||
|
44 | """ | |||
|
45 | ||||
|
46 | def init_signal(self): | |||
|
47 | pass | |||
|
48 | def init_logging(self): | |||
|
49 | pass | |||
|
50 | def reinit_logging(self): | |||
|
51 | pass | |||
|
52 | ||||
|
53 | ||||
|
54 | class ClusterManager(LoggingConfigurable): | |||
|
55 | ||||
|
56 | profiles = Dict() | |||
|
57 | ||||
|
58 | delay = CFloat(1., config=True, | |||
|
59 | help="delay (in s) between starting the controller and the engines") | |||
|
60 | ||||
|
61 | loop = Instance('zmq.eventloop.ioloop.IOLoop') | |||
|
62 | def _loop_default(self): | |||
|
63 | from zmq.eventloop.ioloop import IOLoop | |||
|
64 | return IOLoop.instance() | |||
|
65 | ||||
|
66 | def build_launchers(self, profile_dir): | |||
|
67 | starter = DummyIPClusterStart(log=self.log) | |||
|
68 | starter.initialize(['--profile-dir', profile_dir]) | |||
|
69 | cl = starter.controller_launcher | |||
|
70 | esl = starter.engine_launcher | |||
|
71 | n = starter.n | |||
|
72 | return cl, esl, n | |||
|
73 | ||||
|
74 | def get_profile_dir(self, name, path): | |||
|
75 | p = ProfileDir.find_profile_dir_by_name(path,name=name) | |||
|
76 | return p.location | |||
|
77 | ||||
|
78 | def update_profiles(self): | |||
|
79 | """List all profiles in the ipython_dir and cwd. | |||
|
80 | """ | |||
|
81 | for path in [get_ipython_dir(), os.getcwdu()]: | |||
|
82 | for profile in list_profiles_in(path): | |||
|
83 | pd = self.get_profile_dir(profile, path) | |||
|
84 | if profile not in self.profiles: | |||
|
85 | self.log.debug("Overwriting profile %s" % profile) | |||
|
86 | self.profiles[profile] = { | |||
|
87 | 'profile': profile, | |||
|
88 | 'profile_dir': pd, | |||
|
89 | 'status': 'stopped' | |||
|
90 | } | |||
|
91 | ||||
|
92 | def list_profiles(self): | |||
|
93 | self.update_profiles() | |||
|
94 | result = [self.profile_info(p) for p in self.profiles.keys()] | |||
|
95 | result.sort() | |||
|
96 | return result | |||
|
97 | ||||
|
98 | def check_profile(self, profile): | |||
|
99 | if profile not in self.profiles: | |||
|
100 | raise web.HTTPError(404, u'profile not found') | |||
|
101 | ||||
|
102 | def profile_info(self, profile): | |||
|
103 | self.check_profile(profile) | |||
|
104 | result = {} | |||
|
105 | data = self.profiles.get(profile) | |||
|
106 | result['profile'] = profile | |||
|
107 | result['profile_dir'] = data['profile_dir'] | |||
|
108 | result['status'] = data['status'] | |||
|
109 | if 'n' in data: | |||
|
110 | result['n'] = data['n'] | |||
|
111 | return result | |||
|
112 | ||||
|
113 | def start_cluster(self, profile, n=None): | |||
|
114 | """Start a cluster for a given profile.""" | |||
|
115 | self.check_profile(profile) | |||
|
116 | data = self.profiles[profile] | |||
|
117 | if data['status'] == 'running': | |||
|
118 | raise web.HTTPError(409, u'cluster already running') | |||
|
119 | cl, esl, default_n = self.build_launchers(data['profile_dir']) | |||
|
120 | n = n if n is not None else default_n | |||
|
121 | def clean_data(): | |||
|
122 | data.pop('controller_launcher',None) | |||
|
123 | data.pop('engine_set_launcher',None) | |||
|
124 | data.pop('n',None) | |||
|
125 | data['status'] = 'stopped' | |||
|
126 | def engines_stopped(r): | |||
|
127 | self.log.debug('Engines stopped') | |||
|
128 | if cl.running: | |||
|
129 | cl.stop() | |||
|
130 | clean_data() | |||
|
131 | esl.on_stop(engines_stopped) | |||
|
132 | def controller_stopped(r): | |||
|
133 | self.log.debug('Controller stopped') | |||
|
134 | if esl.running: | |||
|
135 | esl.stop() | |||
|
136 | clean_data() | |||
|
137 | cl.on_stop(controller_stopped) | |||
|
138 | ||||
|
139 | dc = ioloop.DelayedCallback(lambda: cl.start(), 0, self.loop) | |||
|
140 | dc.start() | |||
|
141 | dc = ioloop.DelayedCallback(lambda: esl.start(n), 1000*self.delay, self.loop) | |||
|
142 | dc.start() | |||
|
143 | ||||
|
144 | self.log.debug('Cluster started') | |||
|
145 | data['controller_launcher'] = cl | |||
|
146 | data['engine_set_launcher'] = esl | |||
|
147 | data['n'] = n | |||
|
148 | data['status'] = 'running' | |||
|
149 | return self.profile_info(profile) | |||
|
150 | ||||
|
151 | def stop_cluster(self, profile): | |||
|
152 | """Stop a cluster for a given profile.""" | |||
|
153 | self.check_profile(profile) | |||
|
154 | data = self.profiles[profile] | |||
|
155 | if data['status'] == 'stopped': | |||
|
156 | raise web.HTTPError(409, u'cluster not running') | |||
|
157 | data = self.profiles[profile] | |||
|
158 | cl = data['controller_launcher'] | |||
|
159 | esl = data['engine_set_launcher'] | |||
|
160 | if cl.running: | |||
|
161 | cl.stop() | |||
|
162 | if esl.running: | |||
|
163 | esl.stop() | |||
|
164 | # Return a temp info dict, the real one is updated in the on_stop | |||
|
165 | # logic above. | |||
|
166 | result = { | |||
|
167 | 'profile': data['profile'], | |||
|
168 | 'profile_dir': data['profile_dir'], | |||
|
169 | 'status': 'stopped' | |||
|
170 | } | |||
|
171 | return result | |||
|
172 | ||||
|
173 | def stop_all_clusters(self): | |||
|
174 | for p in self.profiles.keys(): | |||
|
175 | self.stop_cluster(profile) |
@@ -0,0 +1,180 b'' | |||||
|
1 | //---------------------------------------------------------------------------- | |||
|
2 | // Copyright (C) 2008-2011 The IPython Development Team | |||
|
3 | // | |||
|
4 | // Distributed under the terms of the BSD License. The full license is in | |||
|
5 | // the file COPYING, distributed as part of this software. | |||
|
6 | //---------------------------------------------------------------------------- | |||
|
7 | ||||
|
8 | //============================================================================ | |||
|
9 | // NotebookList | |||
|
10 | //============================================================================ | |||
|
11 | ||||
|
12 | var IPython = (function (IPython) { | |||
|
13 | ||||
|
14 | var ClusterList = function (selector) { | |||
|
15 | this.selector = selector; | |||
|
16 | if (this.selector !== undefined) { | |||
|
17 | this.element = $(selector); | |||
|
18 | this.style(); | |||
|
19 | this.bind_events(); | |||
|
20 | } | |||
|
21 | }; | |||
|
22 | ||||
|
23 | ClusterList.prototype.style = function () { | |||
|
24 | $('#cluster_toolbar').addClass('list_toolbar'); | |||
|
25 | $('#cluster_list_info').addClass('toolbar_info'); | |||
|
26 | $('#cluster_buttons').addClass('toolbar_buttons'); | |||
|
27 | $('div#cluster_header').addClass('list_header ui-widget ui-widget-header ui-helper-clearfix'); | |||
|
28 | $('div#cluster_header').children().eq(0).addClass('profile_col'); | |||
|
29 | $('div#cluster_header').children().eq(1).addClass('action_col'); | |||
|
30 | $('div#cluster_header').children().eq(2).addClass('engines_col'); | |||
|
31 | $('div#cluster_header').children().eq(3).addClass('status_col'); | |||
|
32 | $('#refresh_cluster_list').button({ | |||
|
33 | icons : {primary: 'ui-icon-arrowrefresh-1-s'}, | |||
|
34 | text : false | |||
|
35 | }); | |||
|
36 | }; | |||
|
37 | ||||
|
38 | ||||
|
39 | ClusterList.prototype.bind_events = function () { | |||
|
40 | var that = this; | |||
|
41 | $('#refresh_cluster_list').click(function () { | |||
|
42 | that.load_list(); | |||
|
43 | }); | |||
|
44 | }; | |||
|
45 | ||||
|
46 | ||||
|
47 | ClusterList.prototype.load_list = function () { | |||
|
48 | var settings = { | |||
|
49 | processData : false, | |||
|
50 | cache : false, | |||
|
51 | type : "GET", | |||
|
52 | dataType : "json", | |||
|
53 | success : $.proxy(this.load_list_success, this) | |||
|
54 | }; | |||
|
55 | var url = $('body').data('baseProjectUrl') + 'clusters'; | |||
|
56 | $.ajax(url, settings); | |||
|
57 | }; | |||
|
58 | ||||
|
59 | ||||
|
60 | ClusterList.prototype.clear_list = function () { | |||
|
61 | this.element.children('.list_item').remove(); | |||
|
62 | } | |||
|
63 | ||||
|
64 | ClusterList.prototype.load_list_success = function (data, status, xhr) { | |||
|
65 | this.clear_list(); | |||
|
66 | var len = data.length; | |||
|
67 | for (var i=0; i<len; i++) { | |||
|
68 | var item_div = $('<div/>'); | |||
|
69 | var item = new ClusterItem(item_div); | |||
|
70 | item.update_state(data[i]); | |||
|
71 | item_div.data('item', item); | |||
|
72 | this.element.append(item_div); | |||
|
73 | }; | |||
|
74 | }; | |||
|
75 | ||||
|
76 | ||||
|
77 | var ClusterItem = function (element) { | |||
|
78 | this.element = $(element); | |||
|
79 | this.data = null; | |||
|
80 | this.style(); | |||
|
81 | }; | |||
|
82 | ||||
|
83 | ||||
|
84 | ClusterItem.prototype.style = function () { | |||
|
85 | this.element.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix'); | |||
|
86 | this.element.css('border-top-style','none'); | |||
|
87 | } | |||
|
88 | ||||
|
89 | ClusterItem.prototype.update_state = function (data) { | |||
|
90 | this.data = data; | |||
|
91 | if (data.status === 'running') { | |||
|
92 | this.state_running(); | |||
|
93 | } else if (data.status === 'stopped') { | |||
|
94 | this.state_stopped(); | |||
|
95 | }; | |||
|
96 | ||||
|
97 | } | |||
|
98 | ||||
|
99 | ||||
|
100 | ClusterItem.prototype.state_stopped = function () { | |||
|
101 | var that = this; | |||
|
102 | this.element.empty(); | |||
|
103 | var profile_col = $('<span/>').addClass('profile_col').text(this.data.profile); | |||
|
104 | var status_col = $('<span/>').addClass('status_col').html('stopped'); | |||
|
105 | var engines_col = $('<span/>').addClass('engines_col'); | |||
|
106 | var input = $('<input/>').attr('type','text'). | |||
|
107 | attr('size',3).addClass('engine_num_input'); | |||
|
108 | engines_col.append(input); | |||
|
109 | var action_col = $('<span/>').addClass('action_col'); | |||
|
110 | var start_button = $('<button>Start</button>').button(); | |||
|
111 | action_col.append(start_button); | |||
|
112 | this.element.append(profile_col). | |||
|
113 | append(action_col). | |||
|
114 | append(engines_col). | |||
|
115 | append(status_col); | |||
|
116 | start_button.click(function (e) { | |||
|
117 | var n = that.element.find('.engine_num_input').val(); | |||
|
118 | if (!/^\d+$/.test(n) && n.length>0) { | |||
|
119 | status_col.html('invalid engine #'); | |||
|
120 | } else { | |||
|
121 | var settings = { | |||
|
122 | cache : false, | |||
|
123 | data : {n:n}, | |||
|
124 | type : "POST", | |||
|
125 | dataType : "json", | |||
|
126 | success : function (data, status, xhr) { | |||
|
127 | that.update_state(data); | |||
|
128 | }, | |||
|
129 | error : function (data, status, xhr) { | |||
|
130 | status_col.html("error starting cluster") | |||
|
131 | } | |||
|
132 | }; | |||
|
133 | status_col.html('starting'); | |||
|
134 | var url = $('body').data('baseProjectUrl') + 'clusters/' + that.data.profile + '/start'; | |||
|
135 | $.ajax(url, settings); | |||
|
136 | }; | |||
|
137 | }); | |||
|
138 | }; | |||
|
139 | ||||
|
140 | ||||
|
141 | ClusterItem.prototype.state_running = function () { | |||
|
142 | this.element.empty(); | |||
|
143 | var that = this; | |||
|
144 | var profile_col = $('<span/>').addClass('profile_col').text(this.data.profile); | |||
|
145 | var status_col = $('<span/>').addClass('status_col').html('running'); | |||
|
146 | var engines_col = $('<span/>').addClass('engines_col').html(this.data.n); | |||
|
147 | var action_col = $('<span/>').addClass('action_col'); | |||
|
148 | var stop_button = $('<button>Stop</button>').button(); | |||
|
149 | action_col.append(stop_button); | |||
|
150 | this.element.append(profile_col). | |||
|
151 | append(action_col). | |||
|
152 | append(engines_col). | |||
|
153 | append(status_col); | |||
|
154 | stop_button.click(function (e) { | |||
|
155 | var settings = { | |||
|
156 | cache : false, | |||
|
157 | type : "POST", | |||
|
158 | dataType : "json", | |||
|
159 | success : function (data, status, xhr) { | |||
|
160 | that.update_state(data); | |||
|
161 | }, | |||
|
162 | error : function (data, status, xhr) { | |||
|
163 | console.log('error',data); | |||
|
164 | status_col.html("error stopping cluster") | |||
|
165 | } | |||
|
166 | }; | |||
|
167 | status_col.html('stopping') | |||
|
168 | var url = $('body').data('baseProjectUrl') + 'clusters/' + that.data.profile + '/stop'; | |||
|
169 | $.ajax(url, settings); | |||
|
170 | }); | |||
|
171 | }; | |||
|
172 | ||||
|
173 | ||||
|
174 | IPython.ClusterList = ClusterList; | |||
|
175 | IPython.ClusterItem = ClusterItem; | |||
|
176 | ||||
|
177 | return IPython; | |||
|
178 | ||||
|
179 | }(IPython)); | |||
|
180 |
@@ -0,0 +1,83 b'' | |||||
|
1 | //---------------------------------------------------------------------------- | |||
|
2 | // Copyright (C) 2008-2011 The IPython Development Team | |||
|
3 | // | |||
|
4 | // Distributed under the terms of the BSD License. The full license is in | |||
|
5 | // the file COPYING, distributed as part of this software. | |||
|
6 | //---------------------------------------------------------------------------- | |||
|
7 | ||||
|
8 | //============================================================================ | |||
|
9 | // MathJax initialization | |||
|
10 | //============================================================================ | |||
|
11 | ||||
|
12 | var IPython = (function (IPython) { | |||
|
13 | ||||
|
14 | var init_mathjax = function () { | |||
|
15 | if (window.MathJax) { | |||
|
16 | // MathJax loaded | |||
|
17 | MathJax.Hub.Config({ | |||
|
18 | tex2jax: { | |||
|
19 | inlineMath: [ ['$','$'], ["\\(","\\)"] ], | |||
|
20 | displayMath: [ ['$$','$$'], ["\\[","\\]"] ] | |||
|
21 | }, | |||
|
22 | displayAlign: 'left', // Change this to 'center' to center equations. | |||
|
23 | "HTML-CSS": { | |||
|
24 | styles: {'.MathJax_Display': {"margin": 0}} | |||
|
25 | } | |||
|
26 | }); | |||
|
27 | } else if (window.mathjax_url != "") { | |||
|
28 | // Don't have MathJax, but should. Show dialog. | |||
|
29 | var dialog = $('<div></div>') | |||
|
30 | .append( | |||
|
31 | $("<p></p>").addClass('dialog').html( | |||
|
32 | "Math/LaTeX rendering will be disabled." | |||
|
33 | ) | |||
|
34 | ).append( | |||
|
35 | $("<p></p>").addClass('dialog').html( | |||
|
36 | "If you have administrative access to the notebook server and" + | |||
|
37 | " a working internet connection, you can install a local copy" + | |||
|
38 | " of MathJax for offline use with the following command on the server" + | |||
|
39 | " at a Python or IPython prompt:" | |||
|
40 | ) | |||
|
41 | ).append( | |||
|
42 | $("<pre></pre>").addClass('dialog').html( | |||
|
43 | ">>> from IPython.external import mathjax; mathjax.install_mathjax()" | |||
|
44 | ) | |||
|
45 | ).append( | |||
|
46 | $("<p></p>").addClass('dialog').html( | |||
|
47 | "This will try to install MathJax into the IPython source directory." | |||
|
48 | ) | |||
|
49 | ).append( | |||
|
50 | $("<p></p>").addClass('dialog').html( | |||
|
51 | "If IPython is installed to a location that requires" + | |||
|
52 | " administrative privileges to write, you will need to make this call as" + | |||
|
53 | " an administrator, via 'sudo'." | |||
|
54 | ) | |||
|
55 | ).append( | |||
|
56 | $("<p></p>").addClass('dialog').html( | |||
|
57 | "When you start the notebook server, you can instruct it to disable MathJax support altogether:" | |||
|
58 | ) | |||
|
59 | ).append( | |||
|
60 | $("<pre></pre>").addClass('dialog').html( | |||
|
61 | "$ ipython notebook --no-mathjax" | |||
|
62 | ) | |||
|
63 | ).append( | |||
|
64 | $("<p></p>").addClass('dialog').html( | |||
|
65 | "which will prevent this dialog from appearing." | |||
|
66 | ) | |||
|
67 | ).dialog({ | |||
|
68 | title: "Failed to retrieve MathJax from '" + window.mathjax_url + "'", | |||
|
69 | width: "70%", | |||
|
70 | modal: true, | |||
|
71 | }) | |||
|
72 | } else { | |||
|
73 | // No MathJax, but none expected. No dialog. | |||
|
74 | }; | |||
|
75 | }; | |||
|
76 | ||||
|
77 | ||||
|
78 | // Set module variables | |||
|
79 | IPython.init_mathjax = init_mathjax; | |||
|
80 | ||||
|
81 | return IPython; | |||
|
82 | ||||
|
83 | }(IPython)); No newline at end of file |
@@ -0,0 +1,20 b'' | |||||
|
1 | //---------------------------------------------------------------------------- | |||
|
2 | // Copyright (C) 2008-2011 The IPython Development Team | |||
|
3 | // | |||
|
4 | // Distributed under the terms of the BSD License. The full license is in | |||
|
5 | // the file COPYING, distributed as part of this software. | |||
|
6 | //---------------------------------------------------------------------------- | |||
|
7 | ||||
|
8 | //============================================================================ | |||
|
9 | // On document ready | |||
|
10 | //============================================================================ | |||
|
11 | ||||
|
12 | ||||
|
13 | $(document).ready(function () { | |||
|
14 | ||||
|
15 | IPython.page = new IPython.Page(); | |||
|
16 | $('div#main_app').addClass('border-box-sizing ui-widget'); | |||
|
17 | IPython.page.show(); | |||
|
18 | ||||
|
19 | }); | |||
|
20 |
@@ -0,0 +1,59 b'' | |||||
|
1 | //---------------------------------------------------------------------------- | |||
|
2 | // Copyright (C) 2008-2011 The IPython Development Team | |||
|
3 | // | |||
|
4 | // Distributed under the terms of the BSD License. The full license is in | |||
|
5 | // the file COPYING, distributed as part of this software. | |||
|
6 | //---------------------------------------------------------------------------- | |||
|
7 | ||||
|
8 | //============================================================================ | |||
|
9 | // Global header/site setup. | |||
|
10 | //============================================================================ | |||
|
11 | ||||
|
12 | var IPython = (function (IPython) { | |||
|
13 | ||||
|
14 | var Page = function () { | |||
|
15 | this.style(); | |||
|
16 | this.bind_events(); | |||
|
17 | }; | |||
|
18 | ||||
|
19 | Page.prototype.style = function () { | |||
|
20 | $('div#header').addClass('border-box-sizing'). | |||
|
21 | addClass('ui-widget ui-widget-content'). | |||
|
22 | css('border-top-style','none'). | |||
|
23 | css('border-left-style','none'). | |||
|
24 | css('border-right-style','none'); | |||
|
25 | $('div#site').addClass('border-box-sizing') | |||
|
26 | }; | |||
|
27 | ||||
|
28 | ||||
|
29 | Page.prototype.bind_events = function () { | |||
|
30 | }; | |||
|
31 | ||||
|
32 | ||||
|
33 | Page.prototype.show = function () { | |||
|
34 | // The header and site divs start out hidden to prevent FLOUC. | |||
|
35 | // Main scripts should call this method after styling everything. | |||
|
36 | this.show_header(); | |||
|
37 | this.show_site(); | |||
|
38 | }; | |||
|
39 | ||||
|
40 | ||||
|
41 | Page.prototype.show_header = function () { | |||
|
42 | // The header and site divs start out hidden to prevent FLOUC. | |||
|
43 | // Main scripts should call this method after styling everything. | |||
|
44 | $('div#header').css('display','block'); | |||
|
45 | }; | |||
|
46 | ||||
|
47 | ||||
|
48 | Page.prototype.show_site = function () { | |||
|
49 | // The header and site divs start out hidden to prevent FLOUC. | |||
|
50 | // Main scripts should call this method after styling everything. | |||
|
51 | $('div#site').css('display','block'); | |||
|
52 | }; | |||
|
53 | ||||
|
54 | ||||
|
55 | IPython.Page = Page; | |||
|
56 | ||||
|
57 | return IPython; | |||
|
58 | ||||
|
59 | }(IPython)); |
@@ -0,0 +1,19 b'' | |||||
|
1 | //---------------------------------------------------------------------------- | |||
|
2 | // Copyright (C) 2008-2011 The IPython Development Team | |||
|
3 | // | |||
|
4 | // Distributed under the terms of the BSD License. The full license is in | |||
|
5 | // the file COPYING, distributed as part of this software. | |||
|
6 | //---------------------------------------------------------------------------- | |||
|
7 | ||||
|
8 | //============================================================================ | |||
|
9 | // On document ready | |||
|
10 | //============================================================================ | |||
|
11 | ||||
|
12 | ||||
|
13 | $(document).ready(function () { | |||
|
14 | ||||
|
15 | IPython.page = new IPython.Page(); | |||
|
16 | IPython.page.show(); | |||
|
17 | ||||
|
18 | }); | |||
|
19 |
@@ -676,3 +676,27 b' class KVArgParseConfigLoader(ArgParseConfigLoader):' | |||||
676 | sub_parser.load_config(self.extra_args) |
|
676 | sub_parser.load_config(self.extra_args) | |
677 | self.config._merge(sub_parser.config) |
|
677 | self.config._merge(sub_parser.config) | |
678 | self.extra_args = sub_parser.extra_args |
|
678 | self.extra_args = sub_parser.extra_args | |
|
679 | ||||
|
680 | ||||
|
681 | def load_pyconfig_files(config_files, path): | |||
|
682 | """Load multiple Python config files, merging each of them in turn. | |||
|
683 | ||||
|
684 | Parameters | |||
|
685 | ========== | |||
|
686 | config_files : list of str | |||
|
687 | List of config files names to load and merge into the config. | |||
|
688 | path : unicode | |||
|
689 | The full path to the location of the config files. | |||
|
690 | """ | |||
|
691 | config = Config() | |||
|
692 | for cf in config_files: | |||
|
693 | loader = PyFileConfigLoader(cf, path=path) | |||
|
694 | try: | |||
|
695 | next_config = loader.load_config() | |||
|
696 | except ConfigFileNotFound: | |||
|
697 | pass | |||
|
698 | except: | |||
|
699 | raise | |||
|
700 | else: | |||
|
701 | config._merge(next_config) | |||
|
702 | return config |
@@ -92,6 +92,29 b' ipython profile list -h # show the help string for the list subcommand' | |||||
92 | #----------------------------------------------------------------------------- |
|
92 | #----------------------------------------------------------------------------- | |
93 |
|
93 | |||
94 |
|
94 | |||
|
95 | def list_profiles_in(path): | |||
|
96 | """list profiles in a given root directory""" | |||
|
97 | files = os.listdir(path) | |||
|
98 | profiles = [] | |||
|
99 | for f in files: | |||
|
100 | full_path = os.path.join(path, f) | |||
|
101 | if os.path.isdir(full_path) and f.startswith('profile_'): | |||
|
102 | profiles.append(f.split('_',1)[-1]) | |||
|
103 | return profiles | |||
|
104 | ||||
|
105 | ||||
|
106 | def list_bundled_profiles(): | |||
|
107 | """list profiles that are bundled with IPython.""" | |||
|
108 | path = os.path.join(get_ipython_package_dir(), u'config', u'profile') | |||
|
109 | files = os.listdir(path) | |||
|
110 | profiles = [] | |||
|
111 | for profile in files: | |||
|
112 | full_path = os.path.join(path, profile) | |||
|
113 | if os.path.isdir(full_path): | |||
|
114 | profiles.append(profile) | |||
|
115 | return profiles | |||
|
116 | ||||
|
117 | ||||
95 | class ProfileList(Application): |
|
118 | class ProfileList(Application): | |
96 | name = u'ipython-profile' |
|
119 | name = u'ipython-profile' | |
97 | description = list_help |
|
120 | description = list_help | |
@@ -115,35 +138,15 b' class ProfileList(Application):' | |||||
115 | the environment variable IPYTHON_DIR. |
|
138 | the environment variable IPYTHON_DIR. | |
116 | """ |
|
139 | """ | |
117 | ) |
|
140 | ) | |
118 |
|
141 | |||
119 | def _list_profiles_in(self, path): |
|
142 | ||
120 | """list profiles in a given root directory""" |
|
|||
121 | files = os.listdir(path) |
|
|||
122 | profiles = [] |
|
|||
123 | for f in files: |
|
|||
124 | full_path = os.path.join(path, f) |
|
|||
125 | if os.path.isdir(full_path) and f.startswith('profile_'): |
|
|||
126 | profiles.append(f.split('_',1)[-1]) |
|
|||
127 | return profiles |
|
|||
128 |
|
||||
129 | def _list_bundled_profiles(self): |
|
|||
130 | """list profiles in a given root directory""" |
|
|||
131 | path = os.path.join(get_ipython_package_dir(), u'config', u'profile') |
|
|||
132 | files = os.listdir(path) |
|
|||
133 | profiles = [] |
|
|||
134 | for profile in files: |
|
|||
135 | full_path = os.path.join(path, profile) |
|
|||
136 | if os.path.isdir(full_path): |
|
|||
137 | profiles.append(profile) |
|
|||
138 | return profiles |
|
|||
139 |
|
||||
140 | def _print_profiles(self, profiles): |
|
143 | def _print_profiles(self, profiles): | |
141 | """print list of profiles, indented.""" |
|
144 | """print list of profiles, indented.""" | |
142 | for profile in profiles: |
|
145 | for profile in profiles: | |
143 | print ' %s' % profile |
|
146 | print ' %s' % profile | |
144 |
|
147 | |||
145 | def list_profile_dirs(self): |
|
148 | def list_profile_dirs(self): | |
146 |
profiles = |
|
149 | profiles = list_bundled_profiles() | |
147 | if profiles: |
|
150 | if profiles: | |
148 |
|
151 | |||
149 | print "Available profiles in IPython:" |
|
152 | print "Available profiles in IPython:" | |
@@ -153,13 +156,13 b' class ProfileList(Application):' | |||||
153 | print " into your IPython directory (%s)," % self.ipython_dir |
|
156 | print " into your IPython directory (%s)," % self.ipython_dir | |
154 | print " where you can customize it." |
|
157 | print " where you can customize it." | |
155 |
|
158 | |||
156 |
profiles = |
|
159 | profiles = list_profiles_in(self.ipython_dir) | |
157 | if profiles: |
|
160 | if profiles: | |
158 |
|
161 | |||
159 | print "Available profiles in %s:" % self.ipython_dir |
|
162 | print "Available profiles in %s:" % self.ipython_dir | |
160 | self._print_profiles(profiles) |
|
163 | self._print_profiles(profiles) | |
161 |
|
164 | |||
162 |
profiles = |
|
165 | profiles = list_profiles_in(os.getcwdu()) | |
163 | if profiles: |
|
166 | if profiles: | |
164 |
|
167 | |||
165 | print "Available profiles in current directory (%s):" % os.getcwdu() |
|
168 | print "Available profiles in current directory (%s):" % os.getcwdu() |
@@ -1,3 +1,4 b'' | |||||
|
1 | # coding: utf-8 | |||
1 | """Tests for profile-related functions. |
|
2 | """Tests for profile-related functions. | |
2 |
|
3 | |||
3 | Currently only the startup-dir functionality is tested, but more tests should |
|
4 | Currently only the startup-dir functionality is tested, but more tests should | |
@@ -25,9 +26,12 b' import shutil' | |||||
25 | import sys |
|
26 | import sys | |
26 | import tempfile |
|
27 | import tempfile | |
27 |
|
28 | |||
|
29 | from unittest import TestCase | |||
|
30 | ||||
28 | import nose.tools as nt |
|
31 | import nose.tools as nt | |
29 | from nose import SkipTest |
|
32 | from nose import SkipTest | |
30 |
|
33 | |||
|
34 | from IPython.core.profileapp import list_profiles_in, list_bundled_profiles | |||
31 | from IPython.core.profiledir import ProfileDir |
|
35 | from IPython.core.profiledir import ProfileDir | |
32 |
|
36 | |||
33 | from IPython.testing import decorators as dec |
|
37 | from IPython.testing import decorators as dec | |
@@ -79,35 +83,69 b' def win32_without_pywin32():' | |||||
79 | return False |
|
83 | return False | |
80 |
|
84 | |||
81 |
|
85 | |||
82 | @dec.skipif(win32_without_pywin32(), "Test requires pywin32 on Windows") |
|
86 | class ProfileStartupTest(TestCase): | |
83 | def test_startup_py(): |
|
87 | def setUp(self): | |
84 | # create profile dir |
|
88 | # create profile dir | |
85 | pd = ProfileDir.create_profile_dir_by_name(IP_TEST_DIR, 'test') |
|
89 | self.pd = ProfileDir.create_profile_dir_by_name(IP_TEST_DIR, 'test') | |
86 | # write startup python file |
|
90 | self.options = ['--ipython-dir', IP_TEST_DIR, '--profile', 'test'] | |
87 | with open(os.path.join(pd.startup_dir, '00-start.py'), 'w') as f: |
|
91 | self.fname = os.path.join(TMP_TEST_DIR, 'test.py') | |
88 | f.write('zzz=123\n') |
|
92 | ||
89 | # write simple test file, to check that the startup file was run |
|
93 | def tearDown(self): | |
90 | fname = os.path.join(TMP_TEST_DIR, 'test.py') |
|
94 | # We must remove this profile right away so its presence doesn't | |
91 | with open(fname, 'w') as f: |
|
95 | # confuse other tests. | |
92 | f.write(py3compat.doctest_refactor_print('print zzz\n')) |
|
96 | shutil.rmtree(self.pd.location) | |
93 | # validate output |
|
97 | ||
94 | tt.ipexec_validate(fname, '123', '', |
|
98 | def init(self, startup_file, startup, test): | |
95 | options=['--ipython-dir', IP_TEST_DIR, '--profile', 'test']) |
|
99 | # write startup python file | |
96 |
|
100 | with open(os.path.join(self.pd.startup_dir, startup_file), 'w') as f: | ||
97 | @dec.skipif(win32_without_pywin32(), "Test requires pywin32 on Windows") |
|
101 | f.write(startup) | |
98 | def test_startup_ipy(): |
|
102 | # write simple test file, to check that the startup file was run | |
99 | # create profile dir |
|
103 | with open(self.fname, 'w') as f: | |
100 | pd = ProfileDir.create_profile_dir_by_name(IP_TEST_DIR, 'test') |
|
104 | f.write(py3compat.doctest_refactor_print(test)) | |
101 | # write startup ipython file |
|
105 | ||
102 | with open(os.path.join(pd.startup_dir, '00-start.ipy'), 'w') as f: |
|
106 | def validate(self, output): | |
103 | f.write('%profile\n') |
|
107 | tt.ipexec_validate(self.fname, output, '', options=self.options) | |
104 | # write empty script, because we don't need anything to happen |
|
108 | ||
105 | # after the startup file is run |
|
109 | @dec.skipif(win32_without_pywin32(), "Test requires pywin32 on Windows") | |
106 | fname = os.path.join(TMP_TEST_DIR, 'test.py') |
|
110 | def test_startup_py(self): | |
107 | with open(fname, 'w') as f: |
|
111 | self.init('00-start.py', 'zzz=123\n', | |
108 | f.write('') |
|
112 | py3compat.doctest_refactor_print('print zzz\n')) | |
109 | # validate output |
|
113 | self.validate('123') | |
110 | tt.ipexec_validate(fname, 'test', '', |
|
114 | ||
111 | options=['--ipython-dir', IP_TEST_DIR, '--profile', 'test']) |
|
115 | @dec.skipif(win32_without_pywin32(), "Test requires pywin32 on Windows") | |
|
116 | def test_startup_ipy(self): | |||
|
117 | self.init('00-start.ipy', '%profile\n', '') | |||
|
118 | self.validate('test') | |||
|
119 | ||||
112 |
|
120 | |||
|
121 | def test_list_profiles_in(): | |||
|
122 | # No need to remove these directories and files, as they will get nuked in | |||
|
123 | # the module-level teardown. | |||
|
124 | td = tempfile.mkdtemp(dir=TMP_TEST_DIR) | |||
|
125 | td = py3compat.str_to_unicode(td) | |||
|
126 | for name in ('profile_foo', u'profile_ΓΌnicode', 'profile_hello', | |||
|
127 | 'not_a_profile'): | |||
|
128 | os.mkdir(os.path.join(td, name)) | |||
|
129 | with open(os.path.join(td, 'profile_file'), 'w') as f: | |||
|
130 | f.write("I am not a profile directory") | |||
|
131 | profiles = list_profiles_in(td) | |||
113 |
|
132 | |||
|
133 | # unicode normalization can turn u'ΓΌnicode' into u'u\0308nicode', | |||
|
134 | # so only check for *nicode, and that creating a ProfileDir from the | |||
|
135 | # name remains valid | |||
|
136 | found_unicode = False | |||
|
137 | for p in list(profiles): | |||
|
138 | if p.endswith('nicode'): | |||
|
139 | pd = ProfileDir.find_profile_dir_by_name(td, p) | |||
|
140 | profiles.remove(p) | |||
|
141 | found_unicode = True | |||
|
142 | break | |||
|
143 | nt.assert_true(found_unicode) | |||
|
144 | nt.assert_equals(set(profiles), set(['foo', 'hello'])) | |||
|
145 | ||||
|
146 | ||||
|
147 | def test_list_bundled_profiles(): | |||
|
148 | # This variable will need to be updated when a new profile gets bundled | |||
|
149 | bundled_true = [u'cluster', u'math', u'pysh', u'python3', u'sympy'] | |||
|
150 | bundled = sorted(list_bundled_profiles()) | |||
|
151 | nt.assert_equals(bundled, bundled_true) |
@@ -211,6 +211,7 b' class LoginHandler(AuthenticatedHandler):' | |||||
211 | read_only=self.read_only, |
|
211 | read_only=self.read_only, | |
212 | logged_in=self.logged_in, |
|
212 | logged_in=self.logged_in, | |
213 | login_available=self.login_available, |
|
213 | login_available=self.login_available, | |
|
214 | base_project_url=self.application.ipython_app.base_project_url, | |||
214 | message=message |
|
215 | message=message | |
215 | ) |
|
216 | ) | |
216 |
|
217 | |||
@@ -246,6 +247,7 b' class LogoutHandler(AuthenticatedHandler):' | |||||
246 | read_only=self.read_only, |
|
247 | read_only=self.read_only, | |
247 | logged_in=self.logged_in, |
|
248 | logged_in=self.logged_in, | |
248 | login_available=self.login_available, |
|
249 | login_available=self.login_available, | |
|
250 | base_project_url=self.application.ipython_app.base_project_url, | |||
249 | message=message) |
|
251 | message=message) | |
250 |
|
252 | |||
251 |
|
253 | |||
@@ -587,7 +589,6 b' class NotebookRootHandler(AuthenticatedHandler):' | |||||
587 |
|
589 | |||
588 | @authenticate_unless_readonly |
|
590 | @authenticate_unless_readonly | |
589 | def get(self): |
|
591 | def get(self): | |
590 |
|
||||
591 | nbm = self.application.notebook_manager |
|
592 | nbm = self.application.notebook_manager | |
592 | files = nbm.list_notebooks() |
|
593 | files = nbm.list_notebooks() | |
593 | self.finish(jsonapi.dumps(files)) |
|
594 | self.finish(jsonapi.dumps(files)) | |
@@ -661,6 +662,44 b' class NotebookCopyHandler(AuthenticatedHandler):' | |||||
661 | mathjax_url=self.application.ipython_app.mathjax_url, |
|
662 | mathjax_url=self.application.ipython_app.mathjax_url, | |
662 | ) |
|
663 | ) | |
663 |
|
664 | |||
|
665 | ||||
|
666 | #----------------------------------------------------------------------------- | |||
|
667 | # Cluster handlers | |||
|
668 | #----------------------------------------------------------------------------- | |||
|
669 | ||||
|
670 | ||||
|
671 | class MainClusterHandler(AuthenticatedHandler): | |||
|
672 | ||||
|
673 | @web.authenticated | |||
|
674 | def get(self): | |||
|
675 | cm = self.application.cluster_manager | |||
|
676 | self.finish(jsonapi.dumps(cm.list_profiles())) | |||
|
677 | ||||
|
678 | ||||
|
679 | class ClusterProfileHandler(AuthenticatedHandler): | |||
|
680 | ||||
|
681 | @web.authenticated | |||
|
682 | def get(self, profile): | |||
|
683 | cm = self.application.cluster_manager | |||
|
684 | self.finish(jsonapi.dumps(cm.profile_info(profile))) | |||
|
685 | ||||
|
686 | ||||
|
687 | class ClusterActionHandler(AuthenticatedHandler): | |||
|
688 | ||||
|
689 | @web.authenticated | |||
|
690 | def post(self, profile, action): | |||
|
691 | cm = self.application.cluster_manager | |||
|
692 | if action == 'start': | |||
|
693 | n = self.get_argument('n',default=None) | |||
|
694 | if n is None: | |||
|
695 | data = cm.start_cluster(profile) | |||
|
696 | else: | |||
|
697 | data = cm.start_cluster(profile,int(n)) | |||
|
698 | if action == 'stop': | |||
|
699 | data = cm.stop_cluster(profile) | |||
|
700 | self.finish(jsonapi.dumps(data)) | |||
|
701 | ||||
|
702 | ||||
664 | #----------------------------------------------------------------------------- |
|
703 | #----------------------------------------------------------------------------- | |
665 | # RST web service handlers |
|
704 | # RST web service handlers | |
666 | #----------------------------------------------------------------------------- |
|
705 | #----------------------------------------------------------------------------- |
@@ -49,9 +49,11 b' from .handlers import (LoginHandler, LogoutHandler,' | |||||
49 | ProjectDashboardHandler, NewHandler, NamedNotebookHandler, |
|
49 | ProjectDashboardHandler, NewHandler, NamedNotebookHandler, | |
50 | MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler, |
|
50 | MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler, | |
51 | ShellHandler, NotebookRootHandler, NotebookHandler, NotebookCopyHandler, |
|
51 | ShellHandler, NotebookRootHandler, NotebookHandler, NotebookCopyHandler, | |
52 | RSTHandler, AuthenticatedFileHandler, PrintNotebookHandler |
|
52 | RSTHandler, AuthenticatedFileHandler, PrintNotebookHandler, | |
|
53 | MainClusterHandler, ClusterProfileHandler, ClusterActionHandler | |||
53 | ) |
|
54 | ) | |
54 | from .notebookmanager import NotebookManager |
|
55 | from .notebookmanager import NotebookManager | |
|
56 | from .clustermanager import ClusterManager | |||
55 |
|
57 | |||
56 | from IPython.config.application import catch_config_error, boolean_flag |
|
58 | from IPython.config.application import catch_config_error, boolean_flag | |
57 | from IPython.core.application import BaseIPythonApplication |
|
59 | from IPython.core.application import BaseIPythonApplication | |
@@ -74,6 +76,9 b' from IPython.utils import py3compat' | |||||
74 | _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)" |
|
76 | _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)" | |
75 | _kernel_action_regex = r"(?P<action>restart|interrupt)" |
|
77 | _kernel_action_regex = r"(?P<action>restart|interrupt)" | |
76 | _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)" |
|
78 | _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)" | |
|
79 | _profile_regex = r"(?P<profile>[a-zA-Z0-9]+)" | |||
|
80 | _cluster_action_regex = r"(?P<action>start|stop)" | |||
|
81 | ||||
77 |
|
82 | |||
78 | LOCALHOST = '127.0.0.1' |
|
83 | LOCALHOST = '127.0.0.1' | |
79 |
|
84 | |||
@@ -101,7 +106,8 b' def url_path_join(a,b):' | |||||
101 |
|
106 | |||
102 | class NotebookWebApplication(web.Application): |
|
107 | class NotebookWebApplication(web.Application): | |
103 |
|
108 | |||
104 |
def __init__(self, ipython_app, kernel_manager, notebook_manager, |
|
109 | def __init__(self, ipython_app, kernel_manager, notebook_manager, | |
|
110 | cluster_manager, log, | |||
105 | base_project_url, settings_overrides): |
|
111 | base_project_url, settings_overrides): | |
106 | handlers = [ |
|
112 | handlers = [ | |
107 | (r"/", ProjectDashboardHandler), |
|
113 | (r"/", ProjectDashboardHandler), | |
@@ -120,6 +126,9 b' class NotebookWebApplication(web.Application):' | |||||
120 | (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler), |
|
126 | (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler), | |
121 | (r"/rstservice/render", RSTHandler), |
|
127 | (r"/rstservice/render", RSTHandler), | |
122 | (r"/files/(.*)", AuthenticatedFileHandler, {'path' : notebook_manager.notebook_dir}), |
|
128 | (r"/files/(.*)", AuthenticatedFileHandler, {'path' : notebook_manager.notebook_dir}), | |
|
129 | (r"/clusters", MainClusterHandler), | |||
|
130 | (r"/clusters/%s/%s" % (_profile_regex, _cluster_action_regex), ClusterActionHandler), | |||
|
131 | (r"/clusters/%s" % _profile_regex, ClusterProfileHandler), | |||
123 | ] |
|
132 | ] | |
124 | settings = dict( |
|
133 | settings = dict( | |
125 | template_path=os.path.join(os.path.dirname(__file__), "templates"), |
|
134 | template_path=os.path.join(os.path.dirname(__file__), "templates"), | |
@@ -151,10 +160,11 b' class NotebookWebApplication(web.Application):' | |||||
151 | super(NotebookWebApplication, self).__init__(new_handlers, **settings) |
|
160 | super(NotebookWebApplication, self).__init__(new_handlers, **settings) | |
152 |
|
161 | |||
153 | self.kernel_manager = kernel_manager |
|
162 | self.kernel_manager = kernel_manager | |
154 | self.log = log |
|
|||
155 | self.notebook_manager = notebook_manager |
|
163 | self.notebook_manager = notebook_manager | |
|
164 | self.cluster_manager = cluster_manager | |||
156 | self.ipython_app = ipython_app |
|
165 | self.ipython_app = ipython_app | |
157 | self.read_only = self.ipython_app.read_only |
|
166 | self.read_only = self.ipython_app.read_only | |
|
167 | self.log = log | |||
158 |
|
168 | |||
159 |
|
169 | |||
160 | #----------------------------------------------------------------------------- |
|
170 | #----------------------------------------------------------------------------- | |
@@ -395,6 +405,8 b' class NotebookApp(BaseIPythonApplication):' | |||||
395 | ) |
|
405 | ) | |
396 | self.notebook_manager = NotebookManager(config=self.config, log=self.log) |
|
406 | self.notebook_manager = NotebookManager(config=self.config, log=self.log) | |
397 | self.notebook_manager.list_notebooks() |
|
407 | self.notebook_manager.list_notebooks() | |
|
408 | self.cluster_manager = ClusterManager(config=self.config, log=self.log) | |||
|
409 | self.cluster_manager.update_profiles() | |||
398 |
|
410 | |||
399 | def init_logging(self): |
|
411 | def init_logging(self): | |
400 | super(NotebookApp, self).init_logging() |
|
412 | super(NotebookApp, self).init_logging() | |
@@ -406,7 +418,8 b' class NotebookApp(BaseIPythonApplication):' | |||||
406 | def init_webapp(self): |
|
418 | def init_webapp(self): | |
407 | """initialize tornado webapp and httpserver""" |
|
419 | """initialize tornado webapp and httpserver""" | |
408 | self.web_app = NotebookWebApplication( |
|
420 | self.web_app = NotebookWebApplication( | |
409 |
self, self.kernel_manager, self.notebook_manager, |
|
421 | self, self.kernel_manager, self.notebook_manager, | |
|
422 | self.cluster_manager, self.log, | |||
410 | self.base_project_url, self.webapp_settings |
|
423 | self.base_project_url, self.webapp_settings | |
411 | ) |
|
424 | ) | |
412 | if self.certfile: |
|
425 | if self.certfile: |
@@ -1,10 +1,4 b'' | |||||
1 |
|
1 | |||
2 | .border-box-sizing { |
|
|||
3 | box-sizing: border-box; |
|
|||
4 | -moz-box-sizing: border-box; |
|
|||
5 | -webkit-box-sizing: border-box; |
|
|||
6 | } |
|
|||
7 |
|
||||
8 | /* Flexible box model classes */ |
|
2 | /* Flexible box model classes */ | |
9 | /* Taken from Alex Russell http://infrequently.org/2009/08/css-3-progress/ */ |
|
3 | /* Taken from Alex Russell http://infrequently.org/2009/08/css-3-progress/ */ | |
10 |
|
4 | |||
@@ -101,30 +95,3 b'' | |||||
101 | -moz-box-pack: center; |
|
95 | -moz-box-pack: center; | |
102 | box-pack: center; |
|
96 | box-pack: center; | |
103 | } |
|
97 | } | |
104 |
|
||||
105 | .message { |
|
|||
106 | border-width: 1px; |
|
|||
107 | border-style: solid; |
|
|||
108 | text-align: center; |
|
|||
109 | padding: 0.5em; |
|
|||
110 | margin: 0.5em 0; |
|
|||
111 | } |
|
|||
112 |
|
||||
113 | .message.error { |
|
|||
114 | background-color: #FFD3D1; |
|
|||
115 | border-color: red; |
|
|||
116 | } |
|
|||
117 |
|
||||
118 | .message.warning { |
|
|||
119 | background-color: #FFD09E; |
|
|||
120 | border-color: orange; |
|
|||
121 | } |
|
|||
122 |
|
||||
123 | .message.info { |
|
|||
124 | background-color: #CBFFBA; |
|
|||
125 | border-color: green; |
|
|||
126 | } |
|
|||
127 |
|
||||
128 | #content_panel { |
|
|||
129 | margin: 0.5em; |
|
|||
130 | } No newline at end of file |
|
@@ -6,14 +6,6 b'' | |||||
6 |
|
6 | |||
7 |
|
7 | |||
8 | body { |
|
8 | body { | |
9 | background-color: white; |
|
|||
10 | /* This makes sure that the body covers the entire window and needs to |
|
|||
11 | be in a different element than the display: box in wrapper below */ |
|
|||
12 | position: absolute; |
|
|||
13 | left: 0px; |
|
|||
14 | right: 0px; |
|
|||
15 | top: 0px; |
|
|||
16 | bottom: 0px; |
|
|||
17 | overflow: hidden; |
|
9 | overflow: hidden; | |
18 | } |
|
10 | } | |
19 |
|
11 | |||
@@ -31,11 +23,6 b' span#notebook_name {' | |||||
31 | font-size: 146.5%; |
|
23 | font-size: 146.5%; | |
32 | } |
|
24 | } | |
33 |
|
25 | |||
34 | #menubar { |
|
|||
35 | /* Initially hidden to prevent FLOUC */ |
|
|||
36 | display: none; |
|
|||
37 | } |
|
|||
38 |
|
||||
39 | .ui-menubar-item .ui-button .ui-button-text { |
|
26 | .ui-menubar-item .ui-button .ui-button-text { | |
40 | padding: 0.4em 1.0em; |
|
27 | padding: 0.4em 1.0em; | |
41 | font-size: 100%; |
|
28 | font-size: 100%; | |
@@ -69,8 +56,6 b' span#notebook_name {' | |||||
69 | } |
|
56 | } | |
70 |
|
57 | |||
71 | #toolbar { |
|
58 | #toolbar { | |
72 | /* Initially hidden to prevent FLOUC */ |
|
|||
73 | display: none; |
|
|||
74 | padding: 3px 15px; |
|
59 | padding: 3px 15px; | |
75 | } |
|
60 | } | |
76 |
|
61 | |||
@@ -78,6 +63,12 b' span#notebook_name {' | |||||
78 | font-size: 85%; |
|
63 | font-size: 85%; | |
79 | } |
|
64 | } | |
80 |
|
65 | |||
|
66 | ||||
|
67 | div#main_app { | |||
|
68 | width: 100%; | |||
|
69 | position: relative; | |||
|
70 | } | |||
|
71 | ||||
81 | span#quick_help_area { |
|
72 | span#quick_help_area { | |
82 | position: static; |
|
73 | position: static; | |
83 | padding: 5px 0px; |
|
74 | padding: 5px 0px; |
@@ -14,7 +14,7 b' body {' | |||||
14 | right: 0px; |
|
14 | right: 0px; | |
15 | top: 0px; |
|
15 | top: 0px; | |
16 | bottom: 0px; |
|
16 | bottom: 0px; | |
17 |
overflow: |
|
17 | overflow: visible; | |
18 | } |
|
18 | } | |
19 |
|
19 | |||
20 |
|
20 | |||
@@ -41,11 +41,9 b' span#ipython_notebook h1 img {' | |||||
41 | color: black; |
|
41 | color: black; | |
42 | } |
|
42 | } | |
43 |
|
43 | |||
44 | div#main_app { |
|
44 | #site { | |
45 | /* Initially hidden to prevent FLOUC */ |
|
|||
46 | display: none; |
|
|||
47 | width: 100%; |
|
45 | width: 100%; | |
48 | position: relative; |
|
46 | display: none; | |
49 | } |
|
47 | } | |
50 |
|
48 | |||
51 | /* We set the fonts by hand here to override the values in the theme */ |
|
49 | /* We set the fonts by hand here to override the values in the theme */ | |
@@ -63,11 +61,17 b' div#main_app {' | |||||
63 | font-size: 77%; |
|
61 | font-size: 77%; | |
64 | } |
|
62 | } | |
65 |
|
63 | |||
|
64 | input.ui-button { | |||
|
65 | padding: 0.3em 0.9em; | |||
|
66 | } | |||
|
67 | ||||
66 | span#login_widget { |
|
68 | span#login_widget { | |
67 | float: right; |
|
69 | float: right; | |
68 | } |
|
70 | } | |
69 |
|
71 | |||
70 | /* generic class for hidden objects */ |
|
72 | .border-box-sizing { | |
71 | .hidden { |
|
73 | box-sizing: border-box; | |
72 | display: none; |
|
74 | -moz-box-sizing: border-box; | |
|
75 | -webkit-box-sizing: border-box; | |||
73 | } |
|
76 | } | |
|
77 |
@@ -5,70 +5,68 b'' | |||||
5 | * Author: IPython Development Team |
|
5 | * Author: IPython Development Team | |
6 | */ |
|
6 | */ | |
7 |
|
7 | |||
8 |
|
8 | #main_app { | ||
9 | body { |
|
9 | width: 920px; | |
10 | background-color: white; |
|
10 | margin: 30px auto 0px auto; | |
11 | /* This makes sure that the body covers the entire window and needs to |
|
|||
12 | be in a different element than the display: box in wrapper below */ |
|
|||
13 | position: absolute; |
|
|||
14 | left: 0px; |
|
|||
15 | right: 0px; |
|
|||
16 | top: 0px; |
|
|||
17 | bottom: 0px; |
|
|||
18 | overflow: auto; |
|
|||
19 | } |
|
|||
20 |
|
||||
21 | #left_panel { |
|
|||
22 | } |
|
11 | } | |
23 |
|
12 | |||
24 | #drop_zone { |
|
13 | #tabs { | |
25 | height: 200px; |
|
14 | border-style: none; | |
26 | width: 200px |
|
|||
27 | } |
|
15 | } | |
28 |
|
16 | |||
29 | #content_panel { |
|
17 | #tab1, #tab2 { | |
30 | width: 600px; |
|
18 | padding: 1em 0em; | |
31 | } |
|
19 | } | |
32 |
|
20 | |||
33 |
|
|
21 | .list_toolbar { | |
34 | padding: 5px; |
|
22 | padding: 5px; | |
35 | height: 25px; |
|
23 | height: 25px; | |
36 | line-height: 25px; |
|
24 | line-height: 25px; | |
37 | } |
|
25 | } | |
38 |
|
26 | |||
39 | #header_border { |
|
27 | .toolbar_info { | |
40 | width: 100%; |
|
|||
41 | height: 2px; |
|
|||
42 | } |
|
|||
43 |
|
||||
44 | #app_hbox { |
|
|||
45 | width: 100%; |
|
|||
46 | } |
|
|||
47 |
|
||||
48 | #drag_info { |
|
|||
49 | float: left; |
|
28 | float: left; | |
50 | } |
|
29 | } | |
51 |
|
30 | |||
52 |
|
|
31 | .toolbar_buttons { | |
53 | float: right; |
|
32 | float: right; | |
54 | } |
|
33 | } | |
55 |
|
34 | |||
56 | #project_name { |
|
35 | .list_header { | |
57 | height: 25px; |
|
36 | height: 25px; | |
58 | line-height: 25px; |
|
37 | line-height: 25px; | |
59 | padding: 3px; |
|
38 | padding: 3px 5px; | |
60 | } |
|
39 | } | |
61 |
|
40 | |||
62 | .notebook_item { |
|
41 | ||
|
42 | ||||
|
43 | .list_item { | |||
63 | height: 25px; |
|
44 | height: 25px; | |
64 | line-height: 25px; |
|
45 | line-height: 25px; | |
65 | padding: 3px; |
|
46 | padding: 3px 5px; | |
66 | } |
|
47 | } | |
67 |
|
48 | |||
68 | .notebook_item a { |
|
49 | .notebook_item a { | |
69 | text-decoration: none; |
|
50 | text-decoration: none; | |
70 | } |
|
51 | } | |
71 |
|
52 | |||
|
53 | .profile_col { | |||
|
54 | } | |||
|
55 | ||||
|
56 | .status_col { | |||
|
57 | float: right; | |||
|
58 | width: 325px; | |||
|
59 | } | |||
|
60 | ||||
|
61 | .engines_col { | |||
|
62 | float: right; | |||
|
63 | width: 325px; | |||
|
64 | } | |||
|
65 | ||||
|
66 | .action_col { | |||
|
67 | float: right; | |||
|
68 | } | |||
|
69 | ||||
72 | .item_buttons { |
|
70 | .item_buttons { | |
73 | float: right; |
|
71 | float: right; | |
74 | } |
|
72 | } | |
@@ -80,3 +78,7 b' body {' | |||||
80 | .highlight_text { |
|
78 | .highlight_text { | |
81 | color: blue; |
|
79 | color: blue; | |
82 | } |
|
80 | } | |
|
81 | ||||
|
82 | .ui-tabs .ui-tabs-nav li a { | |||
|
83 | padding: .3em .5em; | |||
|
84 | } |
@@ -38,9 +38,9 b' var IPython = (function (IPython) {' | |||||
38 | } else { |
|
38 | } else { | |
39 | toolbar_height = $('div#toolbar').outerHeight(true); |
|
39 | toolbar_height = $('div#toolbar').outerHeight(true); | |
40 | } |
|
40 | } | |
41 |
var app_height = h-header_height-menubar_height-toolbar_height |
|
41 | var app_height = h-header_height-menubar_height-toolbar_height; // content height | |
42 |
|
42 | |||
43 |
$('div#main_app').height(app_height |
|
43 | $('div#main_app').height(app_height); // content+padding+border height | |
44 |
|
44 | |||
45 | var pager_height = IPython.pager.percentage_height*app_height; |
|
45 | var pager_height = IPython.pager.percentage_height*app_height; | |
46 | var pager_splitter_height = $('div#pager_splitter').outerHeight(true); |
|
46 | var pager_splitter_height = $('div#pager_splitter').outerHeight(true); |
@@ -12,19 +12,11 b'' | |||||
12 |
|
12 | |||
13 | $(document).ready(function () { |
|
13 | $(document).ready(function () { | |
14 |
|
14 | |||
15 | $('div#header').addClass('border-box-sizing'); |
|
15 | IPython.page = new IPython.Page(); | |
16 | $('div#header_border').addClass('border-box-sizing ui-widget ui-widget-content'); |
|
16 | $('input#login_submit').button(); | |
17 |
|
||||
18 | $('div#main_app').addClass('border-box-sizing ui-widget'); |
|
17 | $('div#main_app').addClass('border-box-sizing ui-widget'); | |
19 | $('div#app_hbox').addClass('hbox'); |
|
18 | IPython.page.show(); | |
20 |
|
19 | $('input#password_input').focus(); | ||
21 | $('div#left_panel').addClass('box-flex'); |
|
|||
22 | $('div#right_panel').addClass('box-flex'); |
|
|||
23 | $('input#signin').button(); |
|
|||
24 |
|
||||
25 | // These have display: none in the css file and are made visible here to prevent FLOUC. |
|
|||
26 | $('div#header').css('display','block'); |
|
|||
27 | $('div#main_app').css('display','block'); |
|
|||
28 |
|
20 | |||
29 | }); |
|
21 | }); | |
30 |
|
22 |
@@ -24,6 +24,8 b' var IPython = (function (IPython) {' | |||||
24 | this.element.find('button#logout').button(); |
|
24 | this.element.find('button#logout').button(); | |
25 | this.element.find('button#login').button(); |
|
25 | this.element.find('button#login').button(); | |
26 | }; |
|
26 | }; | |
|
27 | ||||
|
28 | ||||
27 | LoginWidget.prototype.bind_events = function () { |
|
29 | LoginWidget.prototype.bind_events = function () { | |
28 | var that = this; |
|
30 | var that = this; | |
29 | this.element.find("button#logout").click(function () { |
|
31 | this.element.find("button#logout").click(function () { |
@@ -22,6 +22,7 b' var IPython = (function (IPython) {' | |||||
22 |
|
22 | |||
23 |
|
23 | |||
24 | MenuBar.prototype.style = function () { |
|
24 | MenuBar.prototype.style = function () { | |
|
25 | this.element.addClass('border-box-sizing'); | |||
25 | $('ul#menus').menubar({ |
|
26 | $('ul#menus').menubar({ | |
26 | select : function (event, ui) { |
|
27 | select : function (event, ui) { | |
27 | // The selected cell loses focus when the menu is entered, so we |
|
28 | // The selected cell loses focus when the menu is entered, so we |
@@ -21,8 +21,14 b' var IPython = (function (IPython) {' | |||||
21 | }; |
|
21 | }; | |
22 |
|
22 | |||
23 | NotebookList.prototype.style = function () { |
|
23 | NotebookList.prototype.style = function () { | |
24 | this.element.addClass('ui-widget ui-widget-content'); |
|
24 | $('#notebook_toolbar').addClass('list_toolbar'); | |
25 | $('div#project_name').addClass('ui-widget ui-widget-header'); |
|
25 | $('#drag_info').addClass('toolbar_info'); | |
|
26 | $('#notebook_buttons').addClass('toolbar_buttons'); | |||
|
27 | $('div#project_name').addClass('list_header ui-widget ui-widget-header'); | |||
|
28 | $('#refresh_notebook_list').button({ | |||
|
29 | icons : {primary: 'ui-icon-arrowrefresh-1-s'}, | |||
|
30 | text : false | |||
|
31 | }); | |||
26 | }; |
|
32 | }; | |
27 |
|
33 | |||
28 |
|
34 | |||
@@ -31,6 +37,9 b' var IPython = (function (IPython) {' | |||||
31 | return; |
|
37 | return; | |
32 | } |
|
38 | } | |
33 | var that = this; |
|
39 | var that = this; | |
|
40 | $('#refresh_notebook_list').click(function () { | |||
|
41 | that.load_list(); | |||
|
42 | }); | |||
34 | this.element.bind('dragover', function () { |
|
43 | this.element.bind('dragover', function () { | |
35 | return false; |
|
44 | return false; | |
36 | }); |
|
45 | }); | |
@@ -62,7 +71,13 b' var IPython = (function (IPython) {' | |||||
62 | }; |
|
71 | }; | |
63 |
|
72 | |||
64 |
|
73 | |||
|
74 | NotebookList.prototype.clear_list = function () { | |||
|
75 | this.element.children('.list_item').remove(); | |||
|
76 | } | |||
|
77 | ||||
|
78 | ||||
65 | NotebookList.prototype.load_list = function () { |
|
79 | NotebookList.prototype.load_list = function () { | |
|
80 | this.clear_list(); | |||
66 | var settings = { |
|
81 | var settings = { | |
67 | processData : false, |
|
82 | processData : false, | |
68 | cache : false, |
|
83 | cache : false, | |
@@ -93,7 +108,8 b' var IPython = (function (IPython) {' | |||||
93 |
|
108 | |||
94 | NotebookList.prototype.new_notebook_item = function (index) { |
|
109 | NotebookList.prototype.new_notebook_item = function (index) { | |
95 | var item = $('<div/>'); |
|
110 | var item = $('<div/>'); | |
96 |
item.addClass(' |
|
111 | item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix'); | |
|
112 | item.css('border-top-style','none'); | |||
97 | var item_name = $('<span/>').addClass('item_name'); |
|
113 | var item_name = $('<span/>').addClass('item_name'); | |
98 |
|
114 | |||
99 | item.append(item_name); |
|
115 | item.append(item_name); | |
@@ -156,7 +172,7 b' var IPython = (function (IPython) {' | |||||
156 | var that = $(this); |
|
172 | var that = $(this); | |
157 | // We use the nbname and notebook_id from the parent notebook_item element's |
|
173 | // We use the nbname and notebook_id from the parent notebook_item element's | |
158 | // data because the outer scopes values change as we iterate through the loop. |
|
174 | // data because the outer scopes values change as we iterate through the loop. | |
159 |
var parent_item = that.parents('div. |
|
175 | var parent_item = that.parents('div.list_item'); | |
160 | var nbname = parent_item.data('nbname'); |
|
176 | var nbname = parent_item.data('nbname'); | |
161 | var notebook_id = parent_item.data('notebook_id'); |
|
177 | var notebook_id = parent_item.data('notebook_id'); | |
162 | var dialog = $('<div/>'); |
|
178 | var dialog = $('<div/>'); |
@@ -11,75 +11,17 b'' | |||||
11 |
|
11 | |||
12 |
|
12 | |||
13 | $(document).ready(function () { |
|
13 | $(document).ready(function () { | |
14 | if (window.MathJax){ |
|
|||
15 | // MathJax loaded |
|
|||
16 | MathJax.Hub.Config({ |
|
|||
17 | tex2jax: { |
|
|||
18 | inlineMath: [ ['$','$'], ["\\(","\\)"] ], |
|
|||
19 | displayMath: [ ['$$','$$'], ["\\[","\\]"] ] |
|
|||
20 | }, |
|
|||
21 | displayAlign: 'left', // Change this to 'center' to center equations. |
|
|||
22 | "HTML-CSS": { |
|
|||
23 | styles: {'.MathJax_Display': {"margin": 0}} |
|
|||
24 | } |
|
|||
25 | }); |
|
|||
26 | }else if (window.mathjax_url != ""){ |
|
|||
27 | // Don't have MathJax, but should. Show dialog. |
|
|||
28 | var dialog = $('<div></div>') |
|
|||
29 | .append( |
|
|||
30 | $("<p></p>").addClass('dialog').html( |
|
|||
31 | "Math/LaTeX rendering will be disabled." |
|
|||
32 | ) |
|
|||
33 | ).append( |
|
|||
34 | $("<p></p>").addClass('dialog').html( |
|
|||
35 | "If you have administrative access to the notebook server and" + |
|
|||
36 | " a working internet connection, you can install a local copy" + |
|
|||
37 | " of MathJax for offline use with the following command on the server" + |
|
|||
38 | " at a Python or IPython prompt:" |
|
|||
39 | ) |
|
|||
40 | ).append( |
|
|||
41 | $("<pre></pre>").addClass('dialog').html( |
|
|||
42 | ">>> from IPython.external import mathjax; mathjax.install_mathjax()" |
|
|||
43 | ) |
|
|||
44 | ).append( |
|
|||
45 | $("<p></p>").addClass('dialog').html( |
|
|||
46 | "This will try to install MathJax into the IPython source directory." |
|
|||
47 | ) |
|
|||
48 | ).append( |
|
|||
49 | $("<p></p>").addClass('dialog').html( |
|
|||
50 | "If IPython is installed to a location that requires" + |
|
|||
51 | " administrative privileges to write, you will need to make this call as" + |
|
|||
52 | " an administrator, via 'sudo'." |
|
|||
53 | ) |
|
|||
54 | ).append( |
|
|||
55 | $("<p></p>").addClass('dialog').html( |
|
|||
56 | "When you start the notebook server, you can instruct it to disable MathJax support altogether:" |
|
|||
57 | ) |
|
|||
58 | ).append( |
|
|||
59 | $("<pre></pre>").addClass('dialog').html( |
|
|||
60 | "$ ipython notebook --no-mathjax" |
|
|||
61 | ) |
|
|||
62 | ).append( |
|
|||
63 | $("<p></p>").addClass('dialog').html( |
|
|||
64 | "which will prevent this dialog from appearing." |
|
|||
65 | ) |
|
|||
66 | ).dialog({ |
|
|||
67 | title: "Failed to retrieve MathJax from '" + window.mathjax_url + "'", |
|
|||
68 | width: "70%", |
|
|||
69 | modal: true, |
|
|||
70 | }) |
|
|||
71 | }else{ |
|
|||
72 | // No MathJax, but none expected. No dialog. |
|
|||
73 | } |
|
|||
74 |
|
||||
75 |
|
14 | |||
76 | IPython.markdown_converter = new Markdown.Converter(); |
|
15 | IPython.init_mathjax(); | |
77 | IPython.read_only = $('meta[name=read_only]').attr("content") == 'True'; |
|
|||
78 |
|
16 | |||
79 | $('div#header').addClass('border-box-sizing'); |
|
17 | IPython.read_only = $('body').data('readOnly') === 'True'; | |
80 |
$('div#main_app').addClass('border-box-sizing ui-widget |
|
18 | $('div#main_app').addClass('border-box-sizing ui-widget'); | |
81 | $('div#notebook_panel').addClass('border-box-sizing ui-widget'); |
|
19 | $('div#notebook_panel').addClass('border-box-sizing ui-widget'); | |
|
20 | // The header's bottom border is provided by the menu bar so we remove it. | |||
|
21 | $('div#header').css('border-bottom-style','none'); | |||
82 |
|
22 | |||
|
23 | IPython.page = new IPython.Page(); | |||
|
24 | IPython.markdown_converter = new Markdown.Converter(); | |||
83 | IPython.layout_manager = new IPython.LayoutManager(); |
|
25 | IPython.layout_manager = new IPython.LayoutManager(); | |
84 | IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter'); |
|
26 | IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter'); | |
85 | IPython.quick_help = new IPython.QuickHelp('span#quick_help_area'); |
|
27 | IPython.quick_help = new IPython.QuickHelp('span#quick_help_area'); | |
@@ -92,22 +34,16 b' $(document).ready(function () {' | |||||
92 |
|
34 | |||
93 | IPython.layout_manager.do_resize(); |
|
35 | IPython.layout_manager.do_resize(); | |
94 |
|
36 | |||
95 | // These have display: none in the css file and are made visible here to prevent FLOUC. |
|
|||
96 | $('div#header').css('display','block'); |
|
|||
97 |
|
||||
98 | if(IPython.read_only){ |
|
37 | if(IPython.read_only){ | |
99 | // hide various elements from read-only view |
|
38 | // hide various elements from read-only view | |
100 | $('div#pager').remove(); |
|
39 | $('div#pager').remove(); | |
101 | $('div#pager_splitter').remove(); |
|
40 | $('div#pager_splitter').remove(); | |
102 | $('span#login_widget').removeClass('hidden'); |
|
|||
103 |
|
41 | |||
104 | // set the notebook name field as not modifiable |
|
42 | // set the notebook name field as not modifiable | |
105 | $('#notebook_name').attr('disabled','disabled') |
|
43 | $('#notebook_name').attr('disabled','disabled') | |
106 | } |
|
44 | } | |
107 |
|
45 | |||
108 | $('div#menubar').css('display','block'); |
|
46 | IPython.page.show(); | |
109 | $('div#toolbar').css('display','block'); |
|
|||
110 | $('div#main_app').css('display','block'); |
|
|||
111 |
|
47 | |||
112 | IPython.layout_manager.do_resize(); |
|
48 | IPython.layout_manager.do_resize(); | |
113 | $([IPython.events]).on('notebook_loaded.Notebook', function () { |
|
49 | $([IPython.events]).on('notebook_loaded.Notebook', function () { |
@@ -11,81 +11,18 b'' | |||||
11 |
|
11 | |||
12 |
|
12 | |||
13 | $(document).ready(function () { |
|
13 | $(document).ready(function () { | |
14 | if (window.MathJax){ |
|
|||
15 | // MathJax loaded |
|
|||
16 | MathJax.Hub.Config({ |
|
|||
17 | tex2jax: { |
|
|||
18 | inlineMath: [ ['$','$'], ["\\(","\\)"] ], |
|
|||
19 | displayMath: [ ['$$','$$'], ["\\[","\\]"] ] |
|
|||
20 | }, |
|
|||
21 | displayAlign: 'left', // Change this to 'center' to center equations. |
|
|||
22 | "HTML-CSS": { |
|
|||
23 | styles: {'.MathJax_Display': {"margin": 0}} |
|
|||
24 | } |
|
|||
25 | }); |
|
|||
26 | }else if (window.mathjax_url != ""){ |
|
|||
27 | // Don't have MathJax, but should. Show dialog. |
|
|||
28 | var dialog = $('<div></div>') |
|
|||
29 | .append( |
|
|||
30 | $("<p></p>").addClass('dialog').html( |
|
|||
31 | "Math/LaTeX rendering will be disabled." |
|
|||
32 | ) |
|
|||
33 | ).append( |
|
|||
34 | $("<p></p>").addClass('dialog').html( |
|
|||
35 | "If you have administrative access to the notebook server and" + |
|
|||
36 | " a working internet connection, you can install a local copy" + |
|
|||
37 | " of MathJax for offline use with the following command on the server" + |
|
|||
38 | " at a Python or IPython prompt:" |
|
|||
39 | ) |
|
|||
40 | ).append( |
|
|||
41 | $("<pre></pre>").addClass('dialog').html( |
|
|||
42 | ">>> from IPython.external import mathjax; mathjax.install_mathjax()" |
|
|||
43 | ) |
|
|||
44 | ).append( |
|
|||
45 | $("<p></p>").addClass('dialog').html( |
|
|||
46 | "This will try to install MathJax into the IPython source directory." |
|
|||
47 | ) |
|
|||
48 | ).append( |
|
|||
49 | $("<p></p>").addClass('dialog').html( |
|
|||
50 | "If IPython is installed to a location that requires" + |
|
|||
51 | " administrative privileges to write, you will need to make this call as" + |
|
|||
52 | " an administrator, via 'sudo'." |
|
|||
53 | ) |
|
|||
54 | ).append( |
|
|||
55 | $("<p></p>").addClass('dialog').html( |
|
|||
56 | "When you start the notebook server, you can instruct it to disable MathJax support altogether:" |
|
|||
57 | ) |
|
|||
58 | ).append( |
|
|||
59 | $("<pre></pre>").addClass('dialog').html( |
|
|||
60 | "$ ipython notebook --no-mathjax" |
|
|||
61 | ) |
|
|||
62 | ).append( |
|
|||
63 | $("<p></p>").addClass('dialog').html( |
|
|||
64 | "which will prevent this dialog from appearing." |
|
|||
65 | ) |
|
|||
66 | ).dialog({ |
|
|||
67 | title: "Failed to retrieve MathJax from '" + window.mathjax_url + "'", |
|
|||
68 | width: "70%", |
|
|||
69 | modal: true, |
|
|||
70 | }) |
|
|||
71 | }else{ |
|
|||
72 | // No MathJax, but none expected. No dialog. |
|
|||
73 | } |
|
|||
74 |
|
||||
75 | IPython.markdown_converter = new Markdown.Converter(); |
|
|||
76 | IPython.read_only = $('meta[name=read_only]').attr("content") == 'True'; |
|
|||
77 |
|
14 | |||
78 | $('div#header').addClass('border-box-sizing'); |
|
15 | IPython.init_mathjax(); | |
79 | $('div#main_app').addClass('border-box-sizing ui-widget ui-widget-content'); |
|
16 | ||
|
17 | IPython.read_only = $('body').data('readOnly') === 'True'; | |||
|
18 | $('div#main_app').addClass('border-box-sizing ui-widget'); | |||
80 | $('div#notebook_panel').addClass('border-box-sizing ui-widget'); |
|
19 | $('div#notebook_panel').addClass('border-box-sizing ui-widget'); | |
81 |
|
20 | |||
|
21 | IPython.page = new IPython.Page(); | |||
|
22 | IPython.markdown_converter = new Markdown.Converter(); | |||
82 | IPython.login_widget = new IPython.LoginWidget('span#login_widget'); |
|
23 | IPython.login_widget = new IPython.LoginWidget('span#login_widget'); | |
83 | IPython.notebook = new IPython.Notebook('div#notebook'); |
|
24 | IPython.notebook = new IPython.Notebook('div#notebook'); | |
84 | IPython.save_widget = new IPython.SaveWidget('span#save_widget'); |
|
25 | IPython.page.show_site(); | |
85 |
|
||||
86 | // These have display: none in the css file and are made visible here to prevent FLOUC. |
|
|||
87 | $('div#header').css('display','block'); |
|
|||
88 | $('div#main_app').css('display','block'); |
|
|||
89 |
|
26 | |||
90 | IPython.notebook.load_notebook($('body').data('notebookId')); |
|
27 | IPython.notebook.load_notebook($('body').data('notebookId')); | |
91 |
|
28 |
@@ -12,31 +12,28 b'' | |||||
12 |
|
12 | |||
13 | $(document).ready(function () { |
|
13 | $(document).ready(function () { | |
14 |
|
14 | |||
15 | $('div#header').addClass('border-box-sizing'); |
|
15 | IPython.page = new IPython.Page(); | |
16 | $('div#header_border').addClass('border-box-sizing ui-widget ui-widget-content'); |
|
|||
17 |
|
16 | |||
|
17 | $('div#tabs').tabs(); | |||
|
18 | $('div#tabs').on('tabsselect', function (event, ui) { | |||
|
19 | var new_url = $('body').data('baseProjectUrl') + '#' + ui.panel.id; | |||
|
20 | window.history.replaceState({}, '', new_url); | |||
|
21 | }); | |||
18 | $('div#main_app').addClass('border-box-sizing ui-widget'); |
|
22 | $('div#main_app').addClass('border-box-sizing ui-widget'); | |
19 | $('div#app_hbox').addClass('hbox'); |
|
23 | $('div#notebooks_toolbar').addClass('ui-widget ui-helper-clearfix'); | |
20 |
|
||||
21 | $('div#content_toolbar').addClass('ui-widget ui-helper-clearfix'); |
|
|||
22 |
|
||||
23 | $('#new_notebook').button().click(function (e) { |
|
24 | $('#new_notebook').button().click(function (e) { | |
24 | window.open($('body').data('baseProjectUrl')+'new'); |
|
25 | window.open($('body').data('baseProjectUrl')+'new'); | |
25 | }); |
|
26 | }); | |
26 |
|
27 | |||
27 | $('div#left_panel').addClass('box-flex'); |
|
28 | IPython.read_only = $('body').data('readOnly') === 'True'; | |
28 | $('div#right_panel').addClass('box-flex'); |
|
|||
29 |
|
||||
30 | IPython.read_only = $('meta[name=read_only]').attr("content") == 'True'; |
|
|||
31 | IPython.notebook_list = new IPython.NotebookList('div#notebook_list'); |
|
29 | IPython.notebook_list = new IPython.NotebookList('div#notebook_list'); | |
|
30 | IPython.cluster_list = new IPython.ClusterList('div#cluster_list'); | |||
32 | IPython.login_widget = new IPython.LoginWidget('span#login_widget'); |
|
31 | IPython.login_widget = new IPython.LoginWidget('span#login_widget'); | |
33 |
|
32 | |||
34 | IPython.notebook_list.load_list(); |
|
33 | IPython.notebook_list.load_list(); | |
|
34 | IPython.cluster_list.load_list(); | |||
35 |
|
35 | |||
36 | // These have display: none in the css file and are made visible here to prevent FLOUC. |
|
36 | IPython.page.show(); | |
37 | $('div#header').css('display','block'); |
|
|||
38 | $('div#main_app').css('display','block'); |
|
|||
39 |
|
||||
40 |
|
37 | |||
41 | }); |
|
38 | }); | |
42 |
|
39 |
@@ -22,7 +22,11 b' var IPython = (function (IPython) {' | |||||
22 |
|
22 | |||
23 |
|
23 | |||
24 | ToolBar.prototype.style = function () { |
|
24 | ToolBar.prototype.style = function () { | |
25 |
this.element.addClass('border-box-sizing') |
|
25 | this.element.addClass('border-box-sizing'). | |
|
26 | addClass('ui-widget ui-widget-content'). | |||
|
27 | css('border-top-style','none'). | |||
|
28 | css('border-left-style','none'). | |||
|
29 | css('border-right-style','none'); | |||
26 | this.element.find('#cell_type').addClass('ui-widget ui-widget-content'); |
|
30 | this.element.find('#cell_type').addClass('ui-widget ui-widget-content'); | |
27 | this.element.find('#save_b').button({ |
|
31 | this.element.find('#save_b').button({ | |
28 | icons : {primary: 'ui-icon-disk'}, |
|
32 | icons : {primary: 'ui-icon-disk'}, |
@@ -1,26 +1,42 b'' | |||||
1 |
{% extends |
|
1 | {% extends page.html %} | |
2 |
|
2 | |||
3 |
{% block |
|
3 | {% block stylesheet %} | |
4 |
|
4 | |||
5 | {% if login_available %} |
|
5 | <link rel="stylesheet" href="{{static_url("css/login.css") }}" type="text/css"/> | |
|
6 | ||||
|
7 | {% end %} | |||
|
8 | ||||
|
9 | ||||
|
10 | {% block login_widget %} | |||
|
11 | {% end %} | |||
|
12 | ||||
|
13 | ||||
|
14 | {% block site %} | |||
6 |
|
15 | |||
|
16 | <div id="main_app"> | |||
|
17 | ||||
|
18 | {% if login_available %} | |||
7 | <form action="/login?next={{url_escape(next)}}" method="post"> |
|
19 | <form action="/login?next={{url_escape(next)}}" method="post"> | |
8 |
Password: <input type="password" name="password" id=" |
|
20 | Password: <input type="password" name="password" id="password_input"> | |
9 |
<input type="submit" value=" |
|
21 | <input type="submit" value="Log in" id="login_submit"> | |
10 | </form> |
|
22 | </form> | |
|
23 | {% end %} | |||
11 |
|
24 | |||
|
25 | {% if message %} | |||
|
26 | {% for key in message %} | |||
|
27 | <div class="message {{key}}"> | |||
|
28 | {{message[key]}} | |||
|
29 | </div> | |||
|
30 | {% end %} | |||
12 | {% end %} |
|
31 | {% end %} | |
13 |
|
32 | |||
14 | {% end %} |
|
33 | <div/> | |
15 |
|
34 | |||
16 | {% block login_widget %} |
|
|||
17 | {% end %} |
|
35 | {% end %} | |
18 |
|
36 | |||
|
37 | ||||
19 | {% block script %} |
|
38 | {% block script %} | |
20 | <script type="text/javascript"> |
|
39 | ||
21 | $(document).ready(function() { |
|
40 | <script src="{{static_url("js/loginmain.js") }}" type="text/javascript" charset="utf-8"></script> | |
22 | IPython.login_widget = new IPython.LoginWidget('span#login_widget'); |
|
41 | ||
23 | $('#focus').focus(); |
|
|||
24 | }); |
|
|||
25 | </script> |
|
|||
26 | {% end %} |
|
42 | {% end %} |
@@ -1,28 +1,40 b'' | |||||
1 |
{% extends |
|
1 | {% extends page.html %} | |
2 |
|
2 | |||
3 |
{% block |
|
3 | {% block stylesheet %} | |
4 | <ul> |
|
|||
5 | {% if read_only or not login_available %} |
|
|||
6 |
|
4 | |||
7 | Proceed to the <a href="/">list of notebooks</a>.</li> |
|
5 | <link rel="stylesheet" href="{{static_url("css/logout.css") }}" type="text/css"/> | |
8 |
|
6 | |||
9 |
|
|
7 | {% end %} | |
|
8 | ||||
|
9 | ||||
|
10 | {% block login_widget %} | |||
|
11 | {% end %} | |||
|
12 | ||||
|
13 | {% block site %} | |||
10 |
|
14 | |||
11 | Proceed to the <a href="/login">login page</a>.</li> |
|
15 | <div id="main_app"> | |
12 |
|
16 | |||
|
17 | {% if message %} | |||
|
18 | {% for key in message %} | |||
|
19 | <div class="message {{key}}"> | |||
|
20 | {{message[key]}} | |||
|
21 | </div> | |||
|
22 | {% end %} | |||
13 | {% end %} |
|
23 | {% end %} | |
14 |
|
24 | |||
15 | </ul> |
|
25 | {% if read_only or not login_available %} | |
|
26 | Proceed to the <a href="/">dashboard</a>. | |||
|
27 | {% else %} | |||
|
28 | Proceed to the <a href="/login">login page</a>. | |||
|
29 | {% end %} | |||
16 |
|
30 | |||
17 | {% end %} |
|
|||
18 |
|
31 | |||
19 | {% block login_widget %} |
|
32 | <div/> | |
|
33 | ||||
20 | {% end %} |
|
34 | {% end %} | |
21 |
|
35 | |||
22 | {% block script %} |
|
36 | {% block script %} | |
23 | <script type="text/javascript"> |
|
37 | ||
24 | $(document).ready(function() { |
|
38 | <script src="{{static_url("js/logoutmain.js") }}" type="text/javascript" charset="utf-8"></script> | |
25 | IPython.login_widget = new IPython.LoginWidget('span#login_widget'); |
|
39 | ||
26 | }); |
|
|||
27 | </script> |
|
|||
28 | {% end %} |
|
40 | {% end %} |
@@ -1,60 +1,49 b'' | |||||
1 | <!DOCTYPE HTML> |
|
1 | {% extends page.html %} | |
2 | <html> |
|
|||
3 |
|
||||
4 | <head> |
|
|||
5 | <meta charset="utf-8"> |
|
|||
6 |
|
||||
7 | <title>IPython Notebook</title> |
|
|||
8 |
|
||||
9 | {% if mathjax_url %} |
|
|||
10 | <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML" charset="utf-8"></script> |
|
|||
11 | {% end %} |
|
|||
12 | <script type="text/javascript"> |
|
|||
13 | // MathJax disabled, set as null to distingish from *missing* MathJax, |
|
|||
14 | // where it will be undefined, and should prompt a dialog later. |
|
|||
15 | window.mathjax_url = "{{mathjax_url}}"; |
|
|||
16 | </script> |
|
|||
17 |
|
||||
18 | <link rel="stylesheet" href="{{ static_url("jquery/css/themes/base/jquery-ui.min.css") }}" type="text/css" /> |
|
|||
19 | <link rel="stylesheet" href="{{ static_url("codemirror/lib/codemirror.css") }}"> |
|
|||
20 | <link rel="stylesheet" href="{{ static_url("codemirror/theme/ipython.css") }}"> |
|
|||
21 |
|
||||
22 | <link rel="stylesheet" href="{{ static_url("prettify/prettify.css") }}"/> |
|
|||
23 |
|
||||
24 | <link rel="stylesheet" href="{{ static_url("css/boilerplate.css") }}" type="text/css" /> |
|
|||
25 | <link rel="stylesheet" href="{{ static_url("css/layout.css") }}" type="text/css" /> |
|
|||
26 | <link rel="stylesheet" href="{{ static_url("css/base.css") }}" type="text/css" /> |
|
|||
27 | <link rel="stylesheet" href="{{ static_url("css/notebook.css") }}" type="text/css" /> |
|
|||
28 | <link rel="stylesheet" href="{{ static_url("css/renderedhtml.css") }}" type="text/css" /> |
|
|||
29 |
|
||||
30 | {% comment In the notebook, the read-only flag is used to determine %} |
|
|||
31 | {% comment whether to hide the side panels and switch off input %} |
|
|||
32 | <meta name="read_only" content="{{read_only and not logged_in}}"/> |
|
|||
33 |
|
||||
34 | </head> |
|
|||
35 |
|
||||
36 | <body |
|
|||
37 | data-project={{project}} data-notebook-id={{notebook_id}} |
|
|||
38 | data-base-project-url={{base_project_url}} data-base-kernel-url={{base_kernel_url}} |
|
|||
39 | > |
|
|||
40 |
|
||||
41 | <div id="header"> |
|
|||
42 | <span id="ipython_notebook"><h1><a href='..' alt='dashboard'><img src='{{static_url("ipynblogo.png")}}' alt='IPython Notebook'/></a></h1></span> |
|
|||
43 | <span id="save_widget"> |
|
|||
44 | <span id="notebook_name"></span> |
|
|||
45 | <span id="save_status"></span> |
|
|||
46 | </span> |
|
|||
47 |
|
2 | |||
48 | <span id="login_widget"> |
|
3 | {% block stylesheet %} | |
49 | {% comment This is a temporary workaround to hide the logout button %} |
|
4 | ||
50 | {% comment when appropriate until notebook.html is templated %} |
|
5 | {% if mathjax_url %} | |
51 | {% if logged_in %} |
|
6 | <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML" charset="utf-8"></script> | |
52 | <button id="logout">Logout</button> |
|
7 | {% end %} | |
53 | {% elif not logged_in and login_available %} |
|
8 | <script type="text/javascript"> | |
54 | <button id="login">Login</button> |
|
9 | // MathJax disabled, set as null to distingish from *missing* MathJax, | |
55 | {% end %} |
|
10 | // where it will be undefined, and should prompt a dialog later. | |
56 | </span> |
|
11 | window.mathjax_url = "{{mathjax_url}}"; | |
57 | </div> |
|
12 | </script> | |
|
13 | ||||
|
14 | <link rel="stylesheet" href="{{ static_url("codemirror/lib/codemirror.css") }}"> | |||
|
15 | <link rel="stylesheet" href="{{ static_url("codemirror/theme/ipython.css") }}"> | |||
|
16 | ||||
|
17 | <link rel="stylesheet" href="{{ static_url("prettify/prettify.css") }}"/> | |||
|
18 | ||||
|
19 | <link rel="stylesheet" href="{{ static_url("css/notebook.css") }}" type="text/css" /> | |||
|
20 | <link rel="stylesheet" href="{{ static_url("css/renderedhtml.css") }}" type="text/css" /> | |||
|
21 | ||||
|
22 | {% end %} | |||
|
23 | ||||
|
24 | ||||
|
25 | {% block params %} | |||
|
26 | ||||
|
27 | data-project={{project}} | |||
|
28 | data-base-project-url={{base_project_url}} | |||
|
29 | data-base-kernel-url={{base_kernel_url}} | |||
|
30 | data-read-only={{read_only and not logged_in}} | |||
|
31 | data-notebook-id={{notebook_id}} | |||
|
32 | ||||
|
33 | {% end %} | |||
|
34 | ||||
|
35 | ||||
|
36 | {% block header %} | |||
|
37 | ||||
|
38 | <span id="save_widget"> | |||
|
39 | <span id="notebook_name"></span> | |||
|
40 | <span id="save_status"></span> | |||
|
41 | </span> | |||
|
42 | ||||
|
43 | {% end %} | |||
|
44 | ||||
|
45 | ||||
|
46 | {% block site %} | |||
58 |
|
47 | |||
59 | <div id="menubar_container"> |
|
48 | <div id="menubar_container"> | |
60 | <div id="menubar"> |
|
49 | <div id="menubar"> | |
@@ -204,8 +193,10 b'' | |||||
204 |
|
193 | |||
205 | </div> |
|
194 | </div> | |
206 |
|
195 | |||
207 | <script src="{{ static_url("jquery/js/jquery-1.7.1.min.js") }}" type="text/javascript" charset="utf-8"></script> |
|
196 | {% end %} | |
208 | <script src="{{ static_url("jquery/js/jquery-ui.min.js") }}" type="text/javascript" charset="utf-8"></script> |
|
197 | ||
|
198 | ||||
|
199 | {% block script %} | |||
209 |
|
200 | |||
210 | <script src="{{ static_url("codemirror/lib/codemirror.js") }}" charset="utf-8"></script> |
|
201 | <script src="{{ static_url("codemirror/lib/codemirror.js") }}" charset="utf-8"></script> | |
211 | <script src="{{ static_url("codemirror/mode/python/python.js") }}" charset="utf-8"></script> |
|
202 | <script src="{{ static_url("codemirror/mode/python/python.js") }}" charset="utf-8"></script> | |
@@ -221,17 +212,16 b'' | |||||
221 | <script src="{{ static_url("prettify/prettify.js") }}" charset="utf-8"></script> |
|
212 | <script src="{{ static_url("prettify/prettify.js") }}" charset="utf-8"></script> | |
222 | <script src="{{ static_url("dateformat/date.format.js") }}" charset="utf-8"></script> |
|
213 | <script src="{{ static_url("dateformat/date.format.js") }}" charset="utf-8"></script> | |
223 |
|
214 | |||
224 | <script src="{{ static_url("js/namespace.js") }}" type="text/javascript" charset="utf-8"></script> |
|
|||
225 | <script src="{{ static_url("js/events.js") }}" type="text/javascript" charset="utf-8"></script> |
|
215 | <script src="{{ static_url("js/events.js") }}" type="text/javascript" charset="utf-8"></script> | |
226 | <script src="{{ static_url("js/utils.js") }}" type="text/javascript" charset="utf-8"></script> |
|
216 | <script src="{{ static_url("js/utils.js") }}" type="text/javascript" charset="utf-8"></script> | |
|
217 | <script src="{{ static_url("js/layoutmanager.js") }}" type="text/javascript" charset="utf-8"></script> | |||
|
218 | <script src="{{ static_url("js/initmathjax.js") }}" type="text/javascript" charset="utf-8"></script> | |||
227 | <script src="{{ static_url("js/cell.js") }}" type="text/javascript" charset="utf-8"></script> |
|
219 | <script src="{{ static_url("js/cell.js") }}" type="text/javascript" charset="utf-8"></script> | |
228 | <script src="{{ static_url("js/codecell.js") }}" type="text/javascript" charset="utf-8"></script> |
|
220 | <script src="{{ static_url("js/codecell.js") }}" type="text/javascript" charset="utf-8"></script> | |
229 | <script src="{{ static_url("js/textcell.js") }}" type="text/javascript" charset="utf-8"></script> |
|
221 | <script src="{{ static_url("js/textcell.js") }}" type="text/javascript" charset="utf-8"></script> | |
230 | <script src="{{ static_url("js/kernel.js") }}" type="text/javascript" charset="utf-8"></script> |
|
222 | <script src="{{ static_url("js/kernel.js") }}" type="text/javascript" charset="utf-8"></script> | |
231 | <script src="{{ static_url("js/layout.js") }}" type="text/javascript" charset="utf-8"></script> |
|
|||
232 | <script src="{{ static_url("js/savewidget.js") }}" type="text/javascript" charset="utf-8"></script> |
|
223 | <script src="{{ static_url("js/savewidget.js") }}" type="text/javascript" charset="utf-8"></script> | |
233 | <script src="{{ static_url("js/quickhelp.js") }}" type="text/javascript" charset="utf-8"></script> |
|
224 | <script src="{{ static_url("js/quickhelp.js") }}" type="text/javascript" charset="utf-8"></script> | |
234 | <script src="{{ static_url("js/loginwidget.js") }}" type="text/javascript" charset="utf-8"></script> |
|
|||
235 | <script src="{{ static_url("js/pager.js") }}" type="text/javascript" charset="utf-8"></script> |
|
225 | <script src="{{ static_url("js/pager.js") }}" type="text/javascript" charset="utf-8"></script> | |
236 | <script src="{{ static_url("js/menubar.js") }}" type="text/javascript" charset="utf-8"></script> |
|
226 | <script src="{{ static_url("js/menubar.js") }}" type="text/javascript" charset="utf-8"></script> | |
237 | <script src="{{ static_url("js/toolbar.js") }}" type="text/javascript" charset="utf-8"></script> |
|
227 | <script src="{{ static_url("js/toolbar.js") }}" type="text/javascript" charset="utf-8"></script> | |
@@ -239,6 +229,5 b'' | |||||
239 | <script src="{{ static_url("js/notificationwidget.js") }}" type="text/javascript" charset="utf-8"></script> |
|
229 | <script src="{{ static_url("js/notificationwidget.js") }}" type="text/javascript" charset="utf-8"></script> | |
240 | <script src="{{ static_url("js/notebookmain.js") }}" type="text/javascript" charset="utf-8"></script> |
|
230 | <script src="{{ static_url("js/notebookmain.js") }}" type="text/javascript" charset="utf-8"></script> | |
241 |
|
231 | |||
242 | </body> |
|
232 | {% end %} | |
243 |
|
233 | |||
244 | </html> |
|
@@ -8,8 +8,8 b'' | |||||
8 |
|
8 | |||
9 | <link rel="stylesheet" href="{{static_url("jquery/css/themes/base/jquery-ui.min.css") }}" type="text/css" /> |
|
9 | <link rel="stylesheet" href="{{static_url("jquery/css/themes/base/jquery-ui.min.css") }}" type="text/css" /> | |
10 | <link rel="stylesheet" href="{{static_url("css/boilerplate.css") }}" type="text/css" /> |
|
10 | <link rel="stylesheet" href="{{static_url("css/boilerplate.css") }}" type="text/css" /> | |
11 |
<link rel="stylesheet" href="{{static_url("css/ |
|
11 | <link rel="stylesheet" href="{{static_url("css/fbm.css") }}" type="text/css" /> | |
12 |
<link rel="stylesheet" href="{{static_url("css/ |
|
12 | <link rel="stylesheet" href="{{static_url("css/page.css") }}" type="text/css"/> | |
13 | {% block stylesheet %} |
|
13 | {% block stylesheet %} | |
14 | {% end %} |
|
14 | {% end %} | |
15 |
|
15 | |||
@@ -21,7 +21,7 b'' | |||||
21 | <body {% block params %}{% end %}> |
|
21 | <body {% block params %}{% end %}> | |
22 |
|
22 | |||
23 | <div id="header"> |
|
23 | <div id="header"> | |
24 | <span id="ipython_notebook"><h1><img src='{{static_url("ipynblogo.png") }}' alt='IPython Notebook'/></h1></span> |
|
24 | <span id="ipython_notebook"><h1><a href={{base_project_url}} alt='dashboard'><img src='{{static_url("ipynblogo.png") }}' alt='IPython Notebook'/></a></h1></span> | |
25 |
|
25 | |||
26 | {% block login_widget %} |
|
26 | {% block login_widget %} | |
27 |
|
27 | |||
@@ -39,43 +39,15 b'' | |||||
39 | {% end %} |
|
39 | {% end %} | |
40 | </div> |
|
40 | </div> | |
41 |
|
41 | |||
42 | <div id="header_border"></div> |
|
42 | <div id="site"> | |
43 |
|
43 | {% block site %} | ||
44 | <div id="main_app"> |
|
44 | {% end %} | |
45 |
|
||||
46 | <div id="app_hbox"> |
|
|||
47 |
|
||||
48 | <div id="left_panel"> |
|
|||
49 | {% block left_panel %} |
|
|||
50 | {% end %} |
|
|||
51 | </div> |
|
|||
52 |
|
||||
53 | <div id="content_panel"> |
|
|||
54 | {% if message %} |
|
|||
55 |
|
||||
56 | {% for key in message %} |
|
|||
57 | <div class="message {{key}}"> |
|
|||
58 | {{message[key]}} |
|
|||
59 | </div> |
|
|||
60 | {% end %} |
|
|||
61 | {% end %} |
|
|||
62 |
|
||||
63 | {% block content_panel %} |
|
|||
64 | {% end %} |
|
|||
65 | </div> |
|
|||
66 | <div id="right_panel"> |
|
|||
67 | {% block right_panel %} |
|
|||
68 | {% end %} |
|
|||
69 | </div> |
|
|||
70 |
|
||||
71 | </div> |
|
|||
72 |
|
||||
73 | </div> |
|
45 | </div> | |
74 |
|
46 | |||
75 | <script src="{{static_url("jquery/js/jquery-1.7.1.min.js") }}" type="text/javascript" charset="utf-8"></script> |
|
47 | <script src="{{static_url("jquery/js/jquery-1.7.1.min.js") }}" type="text/javascript" charset="utf-8"></script> | |
76 | <script src="{{static_url("jquery/js/jquery-ui.min.js") }}" type="text/javascript" charset="utf-8"></script> |
|
48 | <script src="{{static_url("jquery/js/jquery-ui.min.js") }}" type="text/javascript" charset="utf-8"></script> | |
77 | <script src="{{static_url("js/namespace.js") }}" type="text/javascript" charset="utf-8"></script> |
|
49 | <script src="{{static_url("js/namespace.js") }}" type="text/javascript" charset="utf-8"></script> | |
78 |
<script src="{{static_url("js/ |
|
50 | <script src="{{static_url("js/page.js") }}" type="text/javascript" charset="utf-8"></script> | |
79 | <script src="{{static_url("js/loginwidget.js") }}" type="text/javascript" charset="utf-8"></script> |
|
51 | <script src="{{static_url("js/loginwidget.js") }}" type="text/javascript" charset="utf-8"></script> | |
80 |
|
52 | |||
81 | {% block script %} |
|
53 | {% block script %} |
@@ -1,63 +1,44 b'' | |||||
1 | <!DOCTYPE HTML> |
|
1 | {% extends page.html %} | |
2 | <html> |
|
|||
3 |
|
||||
4 | <head> |
|
|||
5 | <meta charset="utf-8"> |
|
|||
6 |
|
||||
7 | <title>IPython Notebook</title> |
|
|||
8 |
|
||||
9 | {% if mathjax_url %} |
|
|||
10 | <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML" charset="utf-8"></script> |
|
|||
11 | {% end %} |
|
|||
12 | <script type="text/javascript"> |
|
|||
13 | // MathJax disabled, set as null to distingish from *missing* MathJax, |
|
|||
14 | // where it will be undefined, and should prompt a dialog later. |
|
|||
15 | window.mathjax_url = "{{mathjax_url}}"; |
|
|||
16 | </script> |
|
|||
17 |
|
||||
18 | <link rel="stylesheet" href="{{ static_url("jquery/css/themes/base/jquery-ui.min.css") }}" type="text/css" /> |
|
|||
19 | <link rel="stylesheet" href="{{ static_url("codemirror/lib/codemirror.css") }}"> |
|
|||
20 | <link rel="stylesheet" href="{{ static_url("codemirror/theme/ipython.css") }}"> |
|
|||
21 |
|
||||
22 | <link rel="stylesheet" href="{{ static_url("prettify/prettify.css") }}"/> |
|
|||
23 |
|
||||
24 | <link rel="stylesheet" href="{{ static_url("css/boilerplate.css") }}" type="text/css" /> |
|
|||
25 | <link rel="stylesheet" href="{{ static_url("css/layout.css") }}" type="text/css" /> |
|
|||
26 | <link rel="stylesheet" href="{{ static_url("css/base.css") }}" type="text/css" /> |
|
|||
27 | <link rel="stylesheet" href="{{ static_url("css/notebook.css") }}" type="text/css" /> |
|
|||
28 | <link rel="stylesheet" href="{{ static_url("css/printnotebook.css") }}" type="text/css" /> |
|
|||
29 | <link rel="stylesheet" href="{{ static_url("css/renderedhtml.css") }}" type="text/css" /> |
|
|||
30 |
|
||||
31 | {% comment In the notebook, the read-only flag is used to determine %} |
|
|||
32 | {% comment whether to hide the side panels and switch off input %} |
|
|||
33 | <meta name="read_only" content="{{read_only and not logged_in}}"/> |
|
|||
34 |
|
||||
35 | </head> |
|
|||
36 |
|
||||
37 | <body |
|
|||
38 | data-project={{project}} data-notebook-id={{notebook_id}} |
|
|||
39 | data-base-project-url={{base_project_url}} data-base-kernel-url={{base_kernel_url}} |
|
|||
40 | > |
|
|||
41 |
|
||||
42 | <div id="header"> |
|
|||
43 | <span id="ipython_notebook"><h1><a href='..' alt='dashboard'><img src='{{static_url("ipynblogo.png") }}' alt='IPython Notebook'/></a></h1></span> |
|
|||
44 | <span id="save_widget"> |
|
|||
45 | <span id="notebook_name"></span> |
|
|||
46 | <span id="save_status"></span> |
|
|||
47 | </span> |
|
|||
48 |
|
||||
49 | <span id="login_widget"> |
|
|||
50 | {% comment This is a temporary workaround to hide the logout button %} |
|
|||
51 | {% comment when appropriate until notebook.html is templated %} |
|
|||
52 | {% if logged_in %} |
|
|||
53 | <button id="logout">Logout</button> |
|
|||
54 | {% elif not logged_in and login_available %} |
|
|||
55 | <button id="login">Login</button> |
|
|||
56 | {% end %} |
|
|||
57 | </span> |
|
|||
58 |
|
2 | |||
59 | </div> |
|
3 | {% block stylesheet %} | |
|
4 | ||||
|
5 | {% if mathjax_url %} | |||
|
6 | <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML" charset="utf-8"></script> | |||
|
7 | {% end %} | |||
|
8 | <script type="text/javascript"> | |||
|
9 | // MathJax disabled, set as null to distingish from *missing* MathJax, | |||
|
10 | // where it will be undefined, and should prompt a dialog later. | |||
|
11 | window.mathjax_url = "{{mathjax_url}}"; | |||
|
12 | </script> | |||
|
13 | ||||
|
14 | <link rel="stylesheet" href="{{ static_url("codemirror/lib/codemirror.css") }}"> | |||
|
15 | <link rel="stylesheet" href="{{ static_url("codemirror/theme/ipython.css") }}"> | |||
|
16 | ||||
|
17 | <link rel="stylesheet" href="{{ static_url("prettify/prettify.css") }}"/> | |||
|
18 | ||||
|
19 | <link rel="stylesheet" href="{{ static_url("css/notebook.css") }}" type="text/css" /> | |||
|
20 | <link rel="stylesheet" href="{{ static_url("css/printnotebook.css") }}" type="text/css" /> | |||
|
21 | <link rel="stylesheet" href="{{ static_url("css/renderedhtml.css") }}" type="text/css" /> | |||
|
22 | ||||
|
23 | {% end %} | |||
|
24 | ||||
|
25 | ||||
|
26 | {% block params %} | |||
60 |
|
27 | |||
|
28 | data-project={{project}} | |||
|
29 | data-base-project-url={{base_project_url}} | |||
|
30 | data-base-kernel-url={{base_kernel_url}} | |||
|
31 | data-read-only={{read_only and not logged_in}} | |||
|
32 | data-notebook-id={{notebook_id}} | |||
|
33 | ||||
|
34 | {% end %} | |||
|
35 | ||||
|
36 | ||||
|
37 | {% block header %} | |||
|
38 | {% end %} | |||
|
39 | ||||
|
40 | ||||
|
41 | {% block site %} | |||
61 |
|
42 | |||
62 | <div id="main_app"> |
|
43 | <div id="main_app"> | |
63 |
|
44 | |||
@@ -67,8 +48,10 b'' | |||||
67 |
|
48 | |||
68 | </div> |
|
49 | </div> | |
69 |
|
50 | |||
70 | <script src="{{ static_url("jquery/js/jquery-1.7.1.min.js") }}" type="text/javascript" charset="utf-8"></script> |
|
51 | {% end %} | |
71 | <script src="{{ static_url("jquery/js/jquery-ui.min.js") }}" type="text/javascript" charset="utf-8"></script> |
|
52 | ||
|
53 | ||||
|
54 | {% block script %} | |||
72 |
|
55 | |||
73 | <script src="{{ static_url("codemirror/lib/codemirror.js") }}" charset="utf-8"></script> |
|
56 | <script src="{{ static_url("codemirror/lib/codemirror.js") }}" charset="utf-8"></script> | |
74 | <script src="{{ static_url("codemirror/mode/python/python.js") }}" charset="utf-8"></script> |
|
57 | <script src="{{ static_url("codemirror/mode/python/python.js") }}" charset="utf-8"></script> | |
@@ -84,19 +67,14 b'' | |||||
84 | <script src="{{ static_url("prettify/prettify.js") }}" charset="utf-8"></script> |
|
67 | <script src="{{ static_url("prettify/prettify.js") }}" charset="utf-8"></script> | |
85 | <script src="{{ static_url("dateformat/date.format.js") }}" charset="utf-8"></script> |
|
68 | <script src="{{ static_url("dateformat/date.format.js") }}" charset="utf-8"></script> | |
86 |
|
69 | |||
87 | <script src="{{ static_url("js/namespace.js") }}" type="text/javascript" charset="utf-8"></script> |
|
|||
88 | <script src="{{ static_url("js/events.js") }}" type="text/javascript" charset="utf-8"></script> |
|
70 | <script src="{{ static_url("js/events.js") }}" type="text/javascript" charset="utf-8"></script> | |
89 | <script src="{{ static_url("js/utils.js") }}" type="text/javascript" charset="utf-8"></script> |
|
71 | <script src="{{ static_url("js/utils.js") }}" type="text/javascript" charset="utf-8"></script> | |
|
72 | <script src="{{ static_url("js/initmathjax.js") }}" type="text/javascript" charset="utf-8"></script> | |||
90 | <script src="{{ static_url("js/cell.js") }}" type="text/javascript" charset="utf-8"></script> |
|
73 | <script src="{{ static_url("js/cell.js") }}" type="text/javascript" charset="utf-8"></script> | |
91 | <script src="{{ static_url("js/codecell.js") }}" type="text/javascript" charset="utf-8"></script> |
|
74 | <script src="{{ static_url("js/codecell.js") }}" type="text/javascript" charset="utf-8"></script> | |
92 | <script src="{{ static_url("js/textcell.js") }}" type="text/javascript" charset="utf-8"></script> |
|
75 | <script src="{{ static_url("js/textcell.js") }}" type="text/javascript" charset="utf-8"></script> | |
93 | <script src="{{ static_url("js/kernel.js") }}" type="text/javascript" charset="utf-8"></script> |
|
76 | <script src="{{ static_url("js/kernel.js") }}" type="text/javascript" charset="utf-8"></script> | |
94 | <script src="{{ static_url("js/kernelstatus.js") }}" type="text/javascript" charset="utf-8"></script> |
|
|||
95 | <script src="{{ static_url("js/savewidget.js") }}" type="text/javascript" charset="utf-8"></script> |
|
|||
96 | <script src="{{ static_url("js/loginwidget.js") }}" type="text/javascript" charset="utf-8"></script> |
|
|||
97 | <script src="{{ static_url("js/notebook.js") }}" type="text/javascript" charset="utf-8"></script> |
|
77 | <script src="{{ static_url("js/notebook.js") }}" type="text/javascript" charset="utf-8"></script> | |
98 | <script src="{{ static_url("js/printnotebookmain.js") }}" type="text/javascript" charset="utf-8"></script> |
|
78 | <script src="{{ static_url("js/printnotebookmain.js") }}" type="text/javascript" charset="utf-8"></script> | |
99 |
|
79 | |||
100 | </body> |
|
80 | {% end %} | |
101 |
|
||||
102 | </html> |
|
@@ -1,43 +1,77 b'' | |||||
1 |
{% extends |
|
1 | {% extends page.html %} | |
2 |
|
2 | |||
3 | {% block title %} |
|
3 | {% block title %}IPython Dashboard{% end %} | |
4 | IPython Dashboard |
|
|||
5 | {% end %} |
|
|||
6 |
|
4 | |||
7 | {% block stylesheet %} |
|
5 | {% block stylesheet %} | |
8 | <link rel="stylesheet" href="{{static_url("css/projectdashboard.css") }}" type="text/css" /> |
|
6 | <link rel="stylesheet" href="{{static_url("css/projectdashboard.css") }}" type="text/css" /> | |
9 | {% end %} |
|
7 | {% end %} | |
10 |
|
8 | |||
11 | {% block meta %} |
|
|||
12 | <meta name="read_only" content="{{read_only}}"/> |
|
|||
13 | {% end %} |
|
|||
14 |
|
9 | |||
15 | {% block params %} |
|
10 | {% block params %} | |
|
11 | ||||
16 | data-project={{project}} |
|
12 | data-project={{project}} | |
17 | data-base-project-url={{base_project_url}} |
|
13 | data-base-project-url={{base_project_url}} | |
18 | data-base-kernel-url={{base_kernel_url}} |
|
14 | data-base-kernel-url={{base_kernel_url}} | |
|
15 | data-read-only={{read_only}} | |||
|
16 | ||||
19 | {% end %} |
|
17 | {% end %} | |
20 |
|
18 | |||
21 | {% block content_panel %} |
|
|||
22 | {% if logged_in or not read_only %} |
|
|||
23 |
|
19 | |||
24 | <div id="content_toolbar"> |
|
20 | {% block site %} | |
25 | <span id="drag_info">Drag files onto the list to import |
|
21 | ||
26 | notebooks.</span> |
|
22 | <div id="main_app"> | |
|
23 | ||||
|
24 | <div id="tabs"> | |||
|
25 | <ul> | |||
|
26 | <li><a href="#tab1">Notebooks</a></li> | |||
|
27 | <li><a href="#tab2">Clusters</a></li> | |||
|
28 | </ul> | |||
27 |
|
29 | |||
28 | <span id="notebooks_buttons"> |
|
30 | <div id="tab1"> | |
29 | <button id="new_notebook">New Notebook</button> |
|
31 | {% if logged_in or not read_only %} | |
30 | </span> |
|
32 | <div id="notebook_toolbar"> | |
|
33 | <span id="drag_info">Drag files onto the list to import | |||
|
34 | notebooks.</span> | |||
|
35 | ||||
|
36 | <span id="notebook_buttons"> | |||
|
37 | <button id="refresh_notebook_list" title="Refresh notebook list">Refresh</button> | |||
|
38 | <button id="new_notebook" title="Create new notebook">New Notebook</button> | |||
|
39 | </span> | |||
|
40 | </div> | |||
|
41 | {% end %} | |||
|
42 | ||||
|
43 | <div id="notebook_list"> | |||
|
44 | <div id="project_name"><h2>{{project}}</h2></div> | |||
|
45 | </div> | |||
31 | </div> |
|
46 | </div> | |
|
47 | <div id="tab2"> | |||
|
48 | ||||
|
49 | <div id="cluster_toolbar"> | |||
|
50 | <span id="cluster_list_info">IPython parallel computing clusters</span> | |||
32 |
|
51 | |||
33 | {% end %} |
|
52 | <span id="cluster_buttons"> | |
|
53 | <button id="refresh_cluster_list" title="Refresh cluster list">Refresh</button> | |||
|
54 | </span> | |||
|
55 | </div> | |||
|
56 | ||||
|
57 | <div id="cluster_list"> | |||
|
58 | <div id="cluster_header"> | |||
|
59 | <span>profile</span> | |||
|
60 | <span>action</span> | |||
|
61 | <span title="Enter the number of engines to start or empty for default"># of engines</span> | |||
|
62 | <span>status</span> | |||
|
63 | </div> | |||
|
64 | </div> | |||
34 |
|
65 | |||
35 | <div id="notebook_list"> |
|
|||
36 | <div id="project_name"><h2>{{project}}</h2></div> |
|
|||
37 | </div> |
|
66 | </div> | |
|
67 | </div> | |||
|
68 | ||||
|
69 | </div> | |||
|
70 | ||||
38 | {% end %} |
|
71 | {% end %} | |
39 |
|
72 | |||
40 | {% block script %} |
|
73 | {% block script %} | |
41 | <script src="{{static_url("js/notebooklist.js") }}" type="text/javascript" charset="utf-8"></script> |
|
74 | <script src="{{static_url("js/notebooklist.js") }}" type="text/javascript" charset="utf-8"></script> | |
|
75 | <script src="{{static_url("js/clusterlist.js") }}" type="text/javascript" charset="utf-8"></script> | |||
42 | <script src="{{static_url("js/projectdashboardmain.js") }}" type="text/javascript" charset="utf-8"></script> |
|
76 | <script src="{{static_url("js/projectdashboardmain.js") }}" type="text/javascript" charset="utf-8"></script> | |
43 | {% end %} |
|
77 | {% end %} |
@@ -106,8 +106,35 b' NO_CLUSTER = 12' | |||||
106 |
|
106 | |||
107 |
|
107 | |||
108 | #----------------------------------------------------------------------------- |
|
108 | #----------------------------------------------------------------------------- | |
|
109 | # Utilities | |||
|
110 | #----------------------------------------------------------------------------- | |||
|
111 | ||||
|
112 | def find_launcher_class(clsname, kind): | |||
|
113 | """Return a launcher for a given clsname and kind. | |||
|
114 | ||||
|
115 | Parameters | |||
|
116 | ========== | |||
|
117 | clsname : str | |||
|
118 | The full name of the launcher class, either with or without the | |||
|
119 | module path, or an abbreviation (MPI, SSH, SGE, PBS, LSF, | |||
|
120 | WindowsHPC). | |||
|
121 | kind : str | |||
|
122 | Either 'EngineSet' or 'Controller'. | |||
|
123 | """ | |||
|
124 | if '.' not in clsname: | |||
|
125 | # not a module, presume it's the raw name in apps.launcher | |||
|
126 | if kind and kind not in clsname: | |||
|
127 | # doesn't match necessary full class name, assume it's | |||
|
128 | # just 'PBS' or 'MPI' prefix: | |||
|
129 | clsname = clsname + kind + 'Launcher' | |||
|
130 | clsname = 'IPython.parallel.apps.launcher.'+clsname | |||
|
131 | klass = import_item(clsname) | |||
|
132 | return klass | |||
|
133 | ||||
|
134 | #----------------------------------------------------------------------------- | |||
109 | # Main application |
|
135 | # Main application | |
110 | #----------------------------------------------------------------------------- |
|
136 | #----------------------------------------------------------------------------- | |
|
137 | ||||
111 | start_help = """Start an IPython cluster for parallel computing |
|
138 | start_help = """Start an IPython cluster for parallel computing | |
112 |
|
139 | |||
113 | Start an ipython cluster by its profile name or cluster |
|
140 | Start an ipython cluster by its profile name or cluster | |
@@ -303,15 +330,8 b' class IPClusterEngines(BaseParallelApplication):' | |||||
303 |
|
330 | |||
304 | def build_launcher(self, clsname, kind=None): |
|
331 | def build_launcher(self, clsname, kind=None): | |
305 | """import and instantiate a Launcher based on importstring""" |
|
332 | """import and instantiate a Launcher based on importstring""" | |
306 | if '.' not in clsname: |
|
|||
307 | # not a module, presume it's the raw name in apps.launcher |
|
|||
308 | if kind and kind not in clsname: |
|
|||
309 | # doesn't match necessary full class name, assume it's |
|
|||
310 | # just 'PBS' or 'MPI' prefix: |
|
|||
311 | clsname = clsname + kind + 'Launcher' |
|
|||
312 | clsname = 'IPython.parallel.apps.launcher.'+clsname |
|
|||
313 | try: |
|
333 | try: | |
314 |
klass = |
|
334 | klass = find_launcher_class(clsname, kind) | |
315 | except (ImportError, KeyError): |
|
335 | except (ImportError, KeyError): | |
316 | self.log.fatal("Could not import launcher class: %r"%clsname) |
|
336 | self.log.fatal("Could not import launcher class: %r"%clsname) | |
317 | self.exit(1) |
|
337 | self.exit(1) | |
@@ -492,7 +512,6 b' class IPClusterStart(IPClusterEngines):' | |||||
492 | def init_launchers(self): |
|
512 | def init_launchers(self): | |
493 | self.controller_launcher = self.build_launcher(self.controller_launcher_class, 'Controller') |
|
513 | self.controller_launcher = self.build_launcher(self.controller_launcher_class, 'Controller') | |
494 | self.engine_launcher = self.build_launcher(self.engine_launcher_class, 'EngineSet') |
|
514 | self.engine_launcher = self.build_launcher(self.engine_launcher_class, 'EngineSet') | |
495 | self.controller_launcher.on_stop(self.stop_launchers) |
|
|||
496 |
|
515 | |||
497 | def engines_stopped(self, r): |
|
516 | def engines_stopped(self, r): | |
498 | """prevent parent.engines_stopped from stopping everything on engine shutdown""" |
|
517 | """prevent parent.engines_stopped from stopping everything on engine shutdown""" | |
@@ -500,6 +519,7 b' class IPClusterStart(IPClusterEngines):' | |||||
500 |
|
519 | |||
501 | def start_controller(self): |
|
520 | def start_controller(self): | |
502 | self.log.info("Starting Controller with %s", self.controller_launcher_class) |
|
521 | self.log.info("Starting Controller with %s", self.controller_launcher_class) | |
|
522 | self.controller_launcher.on_stop(self.stop_launchers) | |||
503 | self.controller_launcher.start() |
|
523 | self.controller_launcher.start() | |
504 |
|
524 | |||
505 | def stop_controller(self): |
|
525 | def stop_controller(self): |
@@ -1164,14 +1164,16 b' class IPClusterLauncher(LocalProcessLauncher):' | |||||
1164 | ipcluster_cmd = List(ipcluster_cmd_argv, config=True, |
|
1164 | ipcluster_cmd = List(ipcluster_cmd_argv, config=True, | |
1165 | help="Popen command for ipcluster") |
|
1165 | help="Popen command for ipcluster") | |
1166 | ipcluster_args = List( |
|
1166 | ipcluster_args = List( | |
1167 | ['--clean-logs', '--log-to-file', '--log-level=%i'%logging.INFO], config=True, |
|
1167 | ['--clean-logs=True', '--log-to-file', '--log-level=%i'%logging.INFO], config=True, | |
1168 | help="Command line arguments to pass to ipcluster.") |
|
1168 | help="Command line arguments to pass to ipcluster.") | |
1169 | ipcluster_subcommand = Unicode('start') |
|
1169 | ipcluster_subcommand = Unicode('start') | |
1170 | ipcluster_n = Integer(2) |
|
1170 | profile = Unicode('default') | |
|
1171 | n = Integer(2) | |||
1171 |
|
1172 | |||
1172 | def find_args(self): |
|
1173 | def find_args(self): | |
1173 | return self.ipcluster_cmd + [self.ipcluster_subcommand] + \ |
|
1174 | return self.ipcluster_cmd + [self.ipcluster_subcommand] + \ | |
1174 |
['--n=%i'%self. |
|
1175 | ['--n=%i'%self.n, '--profile=%s'%self.profile] + \ | |
|
1176 | self.ipcluster_args | |||
1175 |
|
1177 | |||
1176 | def start(self): |
|
1178 | def start(self): | |
1177 | return super(IPClusterLauncher, self).start() |
|
1179 | return super(IPClusterLauncher, self).start() |
General Comments 0
You need to be logged in to leave comments.
Login now