index.tsx 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import * as React from 'react';
  4. import { IIterator, toArray } from '@phosphor/algorithm';
  5. import { ISignal, Signal } from '@phosphor/signaling';
  6. import { ReactWidget, UseSignal } from '@jupyterlab/apputils';
  7. import {
  8. Dialog,
  9. showDialog,
  10. ToolbarButtonComponent
  11. } from '@jupyterlab/apputils';
  12. import { PathExt } from '@jupyterlab/coreutils';
  13. import { ServiceManager, Session, TerminalSession } from '@jupyterlab/services';
  14. import '../style/index.css';
  15. /**
  16. * The class name added to a running widget.
  17. */
  18. const RUNNING_CLASS = 'jp-RunningSessions';
  19. /**
  20. * The class name added to a running widget header.
  21. */
  22. const HEADER_CLASS = 'jp-RunningSessions-header';
  23. /**
  24. * The class name added to the running terminal sessions section.
  25. */
  26. const SECTION_CLASS = 'jp-RunningSessions-section';
  27. /**
  28. * The class name added to the running sessions section header.
  29. */
  30. const SECTION_HEADER_CLASS = 'jp-RunningSessions-sectionHeader';
  31. /**
  32. * The class name added to a section container.
  33. */
  34. const CONTAINER_CLASS = 'jp-RunningSessions-sectionContainer';
  35. /**
  36. * The class name added to the running kernel sessions section list.
  37. */
  38. const LIST_CLASS = 'jp-RunningSessions-sectionList';
  39. /**
  40. * The class name added to the running sessions items.
  41. */
  42. const ITEM_CLASS = 'jp-RunningSessions-item';
  43. /**
  44. * The class name added to a running session item icon.
  45. */
  46. const ITEM_ICON_CLASS = 'jp-RunningSessions-itemIcon';
  47. /**
  48. * The class name added to a running session item label.
  49. */
  50. const ITEM_LABEL_CLASS = 'jp-RunningSessions-itemLabel';
  51. /**
  52. * The class name added to a running session item shutdown button.
  53. */
  54. const SHUTDOWN_BUTTON_CLASS = 'jp-RunningSessions-itemShutdown';
  55. /**
  56. * The class name added to a notebook icon.
  57. */
  58. const NOTEBOOK_ICON_CLASS = 'jp-mod-notebook';
  59. /**
  60. * The class name added to a console icon.
  61. */
  62. const CONSOLE_ICON_CLASS = 'jp-mod-console';
  63. /**
  64. * The class name added to a file icon.
  65. */
  66. const FILE_ICON_CLASS = 'jp-mod-file';
  67. /**
  68. * The class name added to a terminal icon.
  69. */
  70. const TERMINAL_ICON_CLASS = 'jp-mod-terminal';
  71. /**
  72. * Properties for a session list displaying items of generic type `M`.
  73. */
  74. type SessionProps<M> = {
  75. /**
  76. * A signal that tracks when the `open` is clicked on a session item.
  77. */
  78. openRequested: Signal<RunningSessions, M>;
  79. /**
  80. * The session manager.
  81. */
  82. manager: {
  83. /**
  84. * The function called when the shutdown all button is pressed.
  85. */
  86. shutdownAll(): void;
  87. /**
  88. * A signal that should emit a new list of items whenever they are changed.
  89. */
  90. runningChanged: ISignal<any, M[]>;
  91. /**
  92. * Returns a list the running models.
  93. */
  94. running(): IIterator<M>;
  95. };
  96. /**
  97. * The function called when the shutdown button is pressed on an item.
  98. */
  99. shutdown: (model: M) => void;
  100. /**
  101. * The filter that is applied to the items from `runningChanged`.
  102. */
  103. filterRunning?: (model: M) => boolean;
  104. /**
  105. * The name displayed to the user.
  106. */
  107. name: string;
  108. /**
  109. * Returns the icon class for an item.
  110. */
  111. iconClass: (model: M) => string;
  112. /**
  113. * Returns the label for an item.
  114. */
  115. label: (model: M) => string;
  116. /**
  117. * Called to determine the `title` attribute for each item, which is revealed
  118. * on hover.
  119. */
  120. labelTitle?: (model: M) => string;
  121. /**
  122. * Flag that sets whether it sessions should be displayed.
  123. */
  124. available: boolean;
  125. };
  126. function Item<M>(props: SessionProps<M> & { model: M }) {
  127. const { model } = props;
  128. return (
  129. <li className={ITEM_CLASS}>
  130. <span className={`${ITEM_ICON_CLASS} ${props.iconClass(model)}`} />
  131. <span
  132. className={ITEM_LABEL_CLASS}
  133. title={props.labelTitle ? props.labelTitle(model) : ''}
  134. onClick={() => props.openRequested.emit(model)}
  135. >
  136. {props.label(model)}
  137. </span>
  138. <button
  139. className={`${SHUTDOWN_BUTTON_CLASS} jp-mod-styled`}
  140. onClick={() => props.shutdown(model)}
  141. >
  142. SHUT DOWN
  143. </button>
  144. </li>
  145. );
  146. }
  147. function ListView<M>(props: { models: M[] } & SessionProps<M>) {
  148. const { models, ...rest } = props;
  149. return (
  150. <ul className={LIST_CLASS}>
  151. {models.map((m, i) => (
  152. <Item key={i} model={m} {...rest} />
  153. ))}
  154. </ul>
  155. );
  156. }
  157. function List<M>(props: SessionProps<M>) {
  158. const initialModels = toArray(props.manager.running());
  159. const filterRunning = props.filterRunning || (_ => true);
  160. function render(models: Array<M>) {
  161. return <ListView models={models.filter(filterRunning)} {...props} />;
  162. }
  163. if (!props.available) {
  164. return render(initialModels);
  165. }
  166. return (
  167. <UseSignal
  168. signal={props.manager.runningChanged}
  169. initialArgs={initialModels}
  170. >
  171. {(sender: any, args: Array<M>) => render(args)}
  172. </UseSignal>
  173. );
  174. }
  175. /**
  176. * The Section component contains the shared look and feel for an interactive
  177. * list of kernels and sessions.
  178. *
  179. * It is specialized for each based on it's props.
  180. */
  181. function Section<M>(props: SessionProps<M>) {
  182. function onShutdown() {
  183. void showDialog({
  184. title: `Shut Down All ${props.name} Sessions?`,
  185. buttons: [
  186. Dialog.cancelButton(),
  187. Dialog.warnButton({ label: 'SHUT DOWN ALL' })
  188. ]
  189. }).then(result => {
  190. if (result.button.accept) {
  191. props.manager.shutdownAll();
  192. }
  193. });
  194. }
  195. return (
  196. <div className={SECTION_CLASS}>
  197. {props.available && (
  198. <>
  199. <header className={SECTION_HEADER_CLASS}>
  200. <h2>{props.name} Sessions</h2>
  201. <ToolbarButtonComponent
  202. tooltip={`Shut Down All ${props.name} Sessions…`}
  203. iconClassName="jp-CloseIcon"
  204. onClick={onShutdown}
  205. />
  206. </header>
  207. <div className={CONTAINER_CLASS}>
  208. <List {...props} />
  209. </div>
  210. </>
  211. )}
  212. </div>
  213. );
  214. }
  215. interface IRunningSessionsProps {
  216. manager: ServiceManager.IManager;
  217. sessionOpenRequested: Signal<RunningSessions, Session.IModel>;
  218. terminalOpenRequested: Signal<RunningSessions, TerminalSession.IModel>;
  219. }
  220. function RunningSessionsComponent({
  221. manager,
  222. sessionOpenRequested,
  223. terminalOpenRequested
  224. }: IRunningSessionsProps) {
  225. const terminalsAvailable = manager.terminals.isAvailable();
  226. return (
  227. <>
  228. <div className={HEADER_CLASS}>
  229. <ToolbarButtonComponent
  230. tooltip="Refresh List"
  231. iconClassName="jp-RefreshIcon"
  232. onClick={() => {
  233. if (terminalsAvailable) {
  234. void manager.terminals.refreshRunning();
  235. }
  236. void manager.sessions.refreshRunning();
  237. }}
  238. />
  239. </div>
  240. <Section
  241. openRequested={terminalOpenRequested}
  242. manager={manager.terminals}
  243. name="Terminal"
  244. iconClass={() => `${ITEM_ICON_CLASS} ${TERMINAL_ICON_CLASS}`}
  245. label={m => `terminals/${m.name}`}
  246. available={terminalsAvailable}
  247. shutdown={m => manager.terminals.shutdown(m.name)}
  248. />
  249. <Section
  250. openRequested={sessionOpenRequested}
  251. manager={manager.sessions}
  252. filterRunning={m =>
  253. !!((m.name || PathExt.basename(m.path)).indexOf('.') !== -1 || m.name)
  254. }
  255. name="Kernel"
  256. iconClass={m => {
  257. if ((m.name || PathExt.basename(m.path)).indexOf('.ipynb') !== -1) {
  258. return NOTEBOOK_ICON_CLASS;
  259. } else if (m.type.toLowerCase() === 'console') {
  260. return CONSOLE_ICON_CLASS;
  261. }
  262. return FILE_ICON_CLASS;
  263. }}
  264. label={m => m.name || PathExt.basename(m.path)}
  265. available={true}
  266. labelTitle={m => {
  267. let kernelName = m.kernel.name;
  268. if (manager.specs) {
  269. const spec = manager.specs.kernelspecs[kernelName];
  270. kernelName = spec ? spec.display_name : 'unknown';
  271. }
  272. return `Path: ${m.path}\nKernel: ${kernelName}`;
  273. }}
  274. shutdown={m => manager.sessions.shutdown(m.id)}
  275. />
  276. </>
  277. );
  278. }
  279. /**
  280. * A class that exposes the running terminal and kernel sessions.
  281. */
  282. export class RunningSessions extends ReactWidget {
  283. /**
  284. * Construct a new running widget.
  285. */
  286. constructor(options: RunningSessions.IOptions) {
  287. super();
  288. this.options = options;
  289. // this can't be in the react element, because then it would be too nested
  290. this.addClass(RUNNING_CLASS);
  291. }
  292. protected render() {
  293. return (
  294. <RunningSessionsComponent
  295. manager={this.options.manager}
  296. sessionOpenRequested={this._sessionOpenRequested}
  297. terminalOpenRequested={this._terminalOpenRequested}
  298. />
  299. );
  300. }
  301. /**
  302. * A signal emitted when a kernel session open is requested.
  303. */
  304. get sessionOpenRequested(): ISignal<this, Session.IModel> {
  305. return this._sessionOpenRequested;
  306. }
  307. /**
  308. * A signal emitted when a terminal session open is requested.
  309. */
  310. get terminalOpenRequested(): ISignal<this, TerminalSession.IModel> {
  311. return this._terminalOpenRequested;
  312. }
  313. private _sessionOpenRequested = new Signal<this, Session.IModel>(this);
  314. private _terminalOpenRequested = new Signal<this, TerminalSession.IModel>(
  315. this
  316. );
  317. private options: RunningSessions.IOptions;
  318. }
  319. /**
  320. * The namespace for the `RunningSessions` class statics.
  321. */
  322. export namespace RunningSessions {
  323. /**
  324. * An options object for creating a running sessions widget.
  325. */
  326. export interface IOptions {
  327. /**
  328. * A service manager instance.
  329. */
  330. manager: ServiceManager.IManager;
  331. }
  332. }