@jupyterlab/extensionmanager 4.0.0-alpha.12 → 4.0.0-alpha.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/dialog.js +1 -1
- package/lib/index.d.ts +0 -2
- package/lib/index.js +0 -2
- package/lib/index.js.map +1 -1
- package/lib/model.d.ts +81 -194
- package/lib/model.js +191 -455
- package/lib/model.js.map +1 -1
- package/lib/widget.d.ts +18 -207
- package/lib/widget.js +189 -315
- package/lib/widget.js.map +1 -1
- package/package.json +9 -11
- package/style/base.css +40 -98
- package/style/index.css +1 -1
- package/style/index.js +1 -1
- package/lib/build-helper.d.ts +0 -9
- package/lib/build-helper.js +0 -60
- package/lib/build-helper.js.map +0 -1
- package/lib/companions.d.ts +0 -88
- package/lib/companions.js +0 -98
- package/lib/companions.js.map +0 -1
- package/lib/listings.d.ts +0 -50
- package/lib/listings.js +0 -80
- package/lib/listings.js.map +0 -1
- package/lib/npm.d.ts +0 -223
- package/lib/npm.js +0 -76
- package/lib/npm.js.map +0 -1
package/lib/model.js
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
// Copyright (c) Jupyter Development Team.
|
|
2
2
|
// Distributed under the terms of the Modified BSD License.
|
|
3
|
+
/* global RequestInit */
|
|
4
|
+
import { Dialog, showDialog } from '@jupyterlab/apputils';
|
|
5
|
+
import { PageConfig, URLExt } from '@jupyterlab/coreutils';
|
|
3
6
|
import { ServerConnection } from '@jupyterlab/services';
|
|
4
7
|
import { nullTranslator } from '@jupyterlab/translation';
|
|
5
8
|
import { VDomModel } from '@jupyterlab/ui-components';
|
|
6
9
|
import { Debouncer } from '@lumino/polling';
|
|
7
10
|
import * as semver from 'semver';
|
|
8
|
-
import { doBuild } from './build-helper';
|
|
9
|
-
import { presentCompanions } from './companions';
|
|
10
11
|
import { reportInstallError } from './dialog';
|
|
11
|
-
import { Lister } from './listings';
|
|
12
|
-
import { isJupyterOrg, Searcher } from './npm';
|
|
13
12
|
/**
|
|
14
13
|
* The server API path for querying/modifying installed extensions.
|
|
15
14
|
*/
|
|
@@ -18,8 +17,9 @@ const EXTENSION_API_PATH = 'lab/api/extensions';
|
|
|
18
17
|
* Model for an extension list.
|
|
19
18
|
*/
|
|
20
19
|
export class ListModel extends VDomModel {
|
|
21
|
-
constructor(
|
|
20
|
+
constructor(serviceManager, translator) {
|
|
22
21
|
super();
|
|
22
|
+
this.actionError = null;
|
|
23
23
|
/**
|
|
24
24
|
* Contains an error message if an error occurred when querying installed extensions.
|
|
25
25
|
*/
|
|
@@ -29,78 +29,28 @@ export class ListModel extends VDomModel {
|
|
|
29
29
|
*/
|
|
30
30
|
this.searchError = null;
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
32
|
+
* Whether a reload should be considered due to actions taken.
|
|
33
33
|
*/
|
|
34
|
-
this.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
this.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
this.serverRequirementsError = null;
|
|
43
|
-
/**
|
|
44
|
-
* Whether the model has finished async initialization.
|
|
45
|
-
*/
|
|
46
|
-
this.initialized = false;
|
|
47
|
-
/**
|
|
48
|
-
* Whether a fresh build should be considered due to actions taken.
|
|
49
|
-
*/
|
|
50
|
-
this.promptBuild = false;
|
|
51
|
-
this.lister = new Lister();
|
|
52
|
-
this._query = ''; // TODO: we may not need the null case?
|
|
53
|
-
this._page = 0;
|
|
54
|
-
this._pagination = 250;
|
|
55
|
-
this._totalEntries = 0;
|
|
34
|
+
this.promptReload = false;
|
|
35
|
+
this._isDisclaimed = false;
|
|
36
|
+
this._isLoadingInstalledExtensions = false;
|
|
37
|
+
this._isSearching = false;
|
|
38
|
+
this._query = '';
|
|
39
|
+
this._page = 1;
|
|
40
|
+
this._pagination = 30;
|
|
41
|
+
this._lastPage = 1;
|
|
56
42
|
this._pendingActions = [];
|
|
57
|
-
|
|
58
|
-
|
|
43
|
+
const metadata = JSON.parse(
|
|
44
|
+
// The page config option may not be defined; e.g. in the federated example
|
|
45
|
+
PageConfig.getOption('extensionManager') || '{}');
|
|
46
|
+
this.name = metadata.name;
|
|
47
|
+
this.canInstall = metadata.can_install;
|
|
48
|
+
this.installPath = metadata.install_path;
|
|
59
49
|
this.translator = translator || nullTranslator;
|
|
60
|
-
this._app = app;
|
|
61
50
|
this._installed = [];
|
|
62
|
-
this.
|
|
51
|
+
this._lastSearchResult = [];
|
|
63
52
|
this.serviceManager = serviceManager;
|
|
64
|
-
this.
|
|
65
|
-
this._debouncedUpdate = new Debouncer(this.update.bind(this), 1000);
|
|
66
|
-
this.lister.listingsLoaded.connect(this._listingIsLoaded, this);
|
|
67
|
-
this.searcher = new Searcher(settings.composite['npmRegistry'], settings.composite['npmCdn'], settings.composite['enableCdn']);
|
|
68
|
-
_isDisclaimed = settings.composite['disclaimed'] === true;
|
|
69
|
-
settings.changed.connect(() => {
|
|
70
|
-
_isDisclaimed = settings.composite['disclaimed'] === true;
|
|
71
|
-
this.searcher = new Searcher(settings.composite['npmRegistry'], settings.composite['npmCdn'], settings.composite['enableCdn']);
|
|
72
|
-
void this.update();
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
_listingIsLoaded(_, listings) {
|
|
76
|
-
this._listMode = listings.mode;
|
|
77
|
-
this._blockedExtensionsArray = new Array();
|
|
78
|
-
if (this._listMode === 'block') {
|
|
79
|
-
listings.entries.map(e => {
|
|
80
|
-
this._blockedExtensionsArray.push({
|
|
81
|
-
name: e.name,
|
|
82
|
-
regexp: new RegExp(e.name),
|
|
83
|
-
type: e.type,
|
|
84
|
-
reason: e.reason,
|
|
85
|
-
creation_date: e.creation_date,
|
|
86
|
-
last_update_date: e.last_update_date
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
this._allowedExtensionsArray = new Array();
|
|
91
|
-
if (this._listMode === 'allow') {
|
|
92
|
-
listings.entries.map(e => {
|
|
93
|
-
this._allowedExtensionsArray.push({
|
|
94
|
-
name: e.name,
|
|
95
|
-
regexp: new RegExp(e.name),
|
|
96
|
-
type: e.type,
|
|
97
|
-
reason: e.reason,
|
|
98
|
-
creation_date: e.creation_date,
|
|
99
|
-
last_update_date: e.last_update_date
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
void this.initialize();
|
|
53
|
+
this._debouncedSearch = new Debouncer(this.search.bind(this), 1000);
|
|
104
54
|
}
|
|
105
55
|
/**
|
|
106
56
|
* A readonly array of the installed extensions.
|
|
@@ -108,14 +58,33 @@ export class ListModel extends VDomModel {
|
|
|
108
58
|
get installed() {
|
|
109
59
|
return this._installed;
|
|
110
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Whether the warning is disclaimed or not.
|
|
63
|
+
*/
|
|
64
|
+
get isDisclaimed() {
|
|
65
|
+
return this._isDisclaimed;
|
|
66
|
+
}
|
|
67
|
+
set isDisclaimed(v) {
|
|
68
|
+
if (v !== this._isDisclaimed) {
|
|
69
|
+
this._isDisclaimed = v;
|
|
70
|
+
this.stateChanged.emit();
|
|
71
|
+
void this._debouncedSearch.invoke();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
get isLoadingInstalledExtensions() {
|
|
75
|
+
return this._isLoadingInstalledExtensions;
|
|
76
|
+
}
|
|
77
|
+
get isSearching() {
|
|
78
|
+
return this._isSearching;
|
|
79
|
+
}
|
|
111
80
|
/**
|
|
112
81
|
* A readonly array containing the latest search result
|
|
113
82
|
*/
|
|
114
83
|
get searchResult() {
|
|
115
|
-
return this.
|
|
84
|
+
return this._lastSearchResult;
|
|
116
85
|
}
|
|
117
86
|
/**
|
|
118
|
-
* The
|
|
87
|
+
* The search query.
|
|
119
88
|
*
|
|
120
89
|
* Setting its value triggers a new search.
|
|
121
90
|
*/
|
|
@@ -123,29 +92,31 @@ export class ListModel extends VDomModel {
|
|
|
123
92
|
return this._query;
|
|
124
93
|
}
|
|
125
94
|
set query(value) {
|
|
126
|
-
this._query
|
|
127
|
-
|
|
95
|
+
if (this._query !== value) {
|
|
96
|
+
this._query = value;
|
|
97
|
+
this._page = 1;
|
|
98
|
+
void this._debouncedSearch.invoke();
|
|
99
|
+
}
|
|
128
100
|
}
|
|
129
101
|
/**
|
|
130
|
-
* The current
|
|
131
|
-
*
|
|
132
|
-
* The npm repository search is paginated by the `pagination` attribute.
|
|
133
|
-
* The `page` value selects which page is used.
|
|
102
|
+
* The current search page.
|
|
134
103
|
*
|
|
135
104
|
* Setting its value triggers a new search.
|
|
105
|
+
*
|
|
106
|
+
* ### Note
|
|
107
|
+
* First page is 1.
|
|
136
108
|
*/
|
|
137
109
|
get page() {
|
|
138
110
|
return this._page;
|
|
139
111
|
}
|
|
140
112
|
set page(value) {
|
|
141
|
-
this._page
|
|
142
|
-
|
|
113
|
+
if (this._page !== value) {
|
|
114
|
+
this._page = value;
|
|
115
|
+
void this._debouncedSearch.invoke();
|
|
116
|
+
}
|
|
143
117
|
}
|
|
144
118
|
/**
|
|
145
|
-
* The
|
|
146
|
-
*
|
|
147
|
-
* The npm repository search is paginated by the `pagination` attribute.
|
|
148
|
-
* The `page` value selects which page is used.
|
|
119
|
+
* The search pagination.
|
|
149
120
|
*
|
|
150
121
|
* Setting its value triggers a new search.
|
|
151
122
|
*/
|
|
@@ -153,32 +124,16 @@ export class ListModel extends VDomModel {
|
|
|
153
124
|
return this._pagination;
|
|
154
125
|
}
|
|
155
126
|
set pagination(value) {
|
|
156
|
-
this._pagination
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
* The total number of results in the current search.
|
|
161
|
-
*/
|
|
162
|
-
get totalEntries() {
|
|
163
|
-
return this._totalEntries;
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* The list mode.
|
|
167
|
-
*/
|
|
168
|
-
get listMode() {
|
|
169
|
-
return this._listMode;
|
|
170
|
-
}
|
|
171
|
-
/**
|
|
172
|
-
* The total number of blockedExtensions results in the current search.
|
|
173
|
-
*/
|
|
174
|
-
get totalblockedExtensionsFound() {
|
|
175
|
-
return this._totalblockedExtensionsFound;
|
|
127
|
+
if (this._pagination !== value) {
|
|
128
|
+
this._pagination = value;
|
|
129
|
+
void this._debouncedSearch.invoke();
|
|
130
|
+
}
|
|
176
131
|
}
|
|
177
132
|
/**
|
|
178
|
-
* The
|
|
133
|
+
* The last page of results in the current search.
|
|
179
134
|
*/
|
|
180
|
-
get
|
|
181
|
-
return this.
|
|
135
|
+
get lastPage() {
|
|
136
|
+
return this._lastPage;
|
|
182
137
|
}
|
|
183
138
|
/**
|
|
184
139
|
* Dispose the extensions list model.
|
|
@@ -187,23 +142,9 @@ export class ListModel extends VDomModel {
|
|
|
187
142
|
if (this.isDisposed) {
|
|
188
143
|
return;
|
|
189
144
|
}
|
|
190
|
-
this.
|
|
145
|
+
this._debouncedSearch.dispose();
|
|
191
146
|
super.dispose();
|
|
192
147
|
}
|
|
193
|
-
/**
|
|
194
|
-
* Initialize the model.
|
|
195
|
-
*/
|
|
196
|
-
initialize() {
|
|
197
|
-
return this.update()
|
|
198
|
-
.then(() => {
|
|
199
|
-
this.initialized = true;
|
|
200
|
-
this.stateChanged.emit(undefined);
|
|
201
|
-
})
|
|
202
|
-
.catch(() => {
|
|
203
|
-
this.initialized = true;
|
|
204
|
-
this.stateChanged.emit(undefined);
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
148
|
/**
|
|
208
149
|
* Whether there are currently any actions pending.
|
|
209
150
|
*/
|
|
@@ -216,24 +157,11 @@ export class ListModel extends VDomModel {
|
|
|
216
157
|
* @param entry An entry indicating which extension to install.
|
|
217
158
|
*/
|
|
218
159
|
async install(entry) {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if (data.status !== 'ok') {
|
|
223
|
-
reportInstallError(entry.name, data.message, this.translator);
|
|
224
|
-
}
|
|
225
|
-
return this.update();
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
await this.checkCompanionPackages(entry).then(shouldInstall => {
|
|
229
|
-
if (shouldInstall) {
|
|
230
|
-
return this._performAction('install', entry).then(data => {
|
|
231
|
-
if (data.status !== 'ok') {
|
|
232
|
-
reportInstallError(entry.name, data.message, this.translator);
|
|
233
|
-
}
|
|
234
|
-
return this.update();
|
|
235
|
-
});
|
|
160
|
+
await this.performAction('install', entry).then(data => {
|
|
161
|
+
if (data.status !== 'ok') {
|
|
162
|
+
reportInstallError(entry.name, data.message, this.translator);
|
|
236
163
|
}
|
|
164
|
+
return this.update(true);
|
|
237
165
|
});
|
|
238
166
|
}
|
|
239
167
|
/**
|
|
@@ -245,8 +173,8 @@ export class ListModel extends VDomModel {
|
|
|
245
173
|
if (!entry.installed) {
|
|
246
174
|
throw new Error(`Not installed, cannot uninstall: ${entry.name}`);
|
|
247
175
|
}
|
|
248
|
-
await this.
|
|
249
|
-
return this.update();
|
|
176
|
+
await this.performAction('uninstall', entry);
|
|
177
|
+
return this.update(true);
|
|
250
178
|
}
|
|
251
179
|
/**
|
|
252
180
|
* Enable an extension.
|
|
@@ -257,8 +185,8 @@ export class ListModel extends VDomModel {
|
|
|
257
185
|
if (entry.enabled) {
|
|
258
186
|
throw new Error(`Already enabled: ${entry.name}`);
|
|
259
187
|
}
|
|
260
|
-
await this.
|
|
261
|
-
await this.
|
|
188
|
+
await this.performAction('enable', entry);
|
|
189
|
+
await this.refreshInstalled(true);
|
|
262
190
|
}
|
|
263
191
|
/**
|
|
264
192
|
* Disable an extension.
|
|
@@ -269,275 +197,87 @@ export class ListModel extends VDomModel {
|
|
|
269
197
|
if (!entry.enabled) {
|
|
270
198
|
throw new Error(`Already disabled: ${entry.name}`);
|
|
271
199
|
}
|
|
272
|
-
await this.
|
|
273
|
-
await this.
|
|
200
|
+
await this.performAction('disable', entry);
|
|
201
|
+
await this.refreshInstalled(true);
|
|
274
202
|
}
|
|
275
203
|
/**
|
|
276
|
-
*
|
|
204
|
+
* Refresh installed packages
|
|
277
205
|
*
|
|
278
|
-
* @param
|
|
279
|
-
*/
|
|
280
|
-
checkCompanionPackages(entry) {
|
|
281
|
-
return this.searcher
|
|
282
|
-
.fetchPackageData(entry.name, entry.latest_version)
|
|
283
|
-
.then(data => {
|
|
284
|
-
if (!data || !data.jupyterlab || !data.jupyterlab.discovery) {
|
|
285
|
-
return true;
|
|
286
|
-
}
|
|
287
|
-
const discovery = data.jupyterlab.discovery;
|
|
288
|
-
const kernelCompanions = [];
|
|
289
|
-
if (discovery.kernel) {
|
|
290
|
-
// match specs
|
|
291
|
-
for (const kernelInfo of discovery.kernel) {
|
|
292
|
-
const matches = Private.matchSpecs(kernelInfo, this.serviceManager.kernelspecs.specs);
|
|
293
|
-
kernelCompanions.push({ kernelInfo, kernels: matches });
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
if (kernelCompanions.length < 1 && !discovery.server) {
|
|
297
|
-
return true;
|
|
298
|
-
}
|
|
299
|
-
return presentCompanions(kernelCompanions, discovery.server, this.translator);
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
/**
|
|
303
|
-
* Trigger a build check to incorporate actions taken.
|
|
206
|
+
* @param force Force refreshing the list of installed packages
|
|
304
207
|
*/
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
return doBuild(this._app, builder);
|
|
313
|
-
}
|
|
314
|
-
if (response.status !== 'needed') {
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
if (!this.promptBuild) {
|
|
318
|
-
this.promptBuild = true;
|
|
319
|
-
this.stateChanged.emit(undefined);
|
|
320
|
-
}
|
|
208
|
+
async refreshInstalled(force = false) {
|
|
209
|
+
this.installedError = null;
|
|
210
|
+
this._isLoadingInstalledExtensions = true;
|
|
211
|
+
this.stateChanged.emit();
|
|
212
|
+
try {
|
|
213
|
+
const [extensions] = await Private.requestAPI({
|
|
214
|
+
refresh: force ? 1 : 0
|
|
321
215
|
});
|
|
322
|
-
this.
|
|
216
|
+
this._installed = extensions.sort(Private.comparator);
|
|
323
217
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
* Perform a build on the server
|
|
327
|
-
*/
|
|
328
|
-
performBuild() {
|
|
329
|
-
if (this.promptBuild) {
|
|
330
|
-
this.promptBuild = false;
|
|
331
|
-
this.stateChanged.emit(undefined);
|
|
332
|
-
}
|
|
333
|
-
const completed = doBuild(this._app, this.serviceManager.builder);
|
|
334
|
-
this._addPendingAction(completed);
|
|
335
|
-
}
|
|
336
|
-
/**
|
|
337
|
-
* Ignore a build recommendation
|
|
338
|
-
*/
|
|
339
|
-
ignoreBuildRecommendation() {
|
|
340
|
-
if (this.promptBuild) {
|
|
341
|
-
this.promptBuild = false;
|
|
342
|
-
this.stateChanged.emit(undefined);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
/**
|
|
346
|
-
* Ignore a build recommendation
|
|
347
|
-
*/
|
|
348
|
-
refreshInstalled() {
|
|
349
|
-
const refresh = this.update(true);
|
|
350
|
-
this._addPendingAction(refresh);
|
|
351
|
-
}
|
|
352
|
-
/**
|
|
353
|
-
* Translate search results from an npm repository query into entries
|
|
354
|
-
* and remove entries with 'deprecated' in the keyword list
|
|
355
|
-
*
|
|
356
|
-
* @param res Promise to an npm query result.
|
|
357
|
-
*/
|
|
358
|
-
async translateSearchResult(res) {
|
|
359
|
-
const entries = {};
|
|
360
|
-
this._totalblockedExtensionsFound = 0;
|
|
361
|
-
this._totalallowedExtensionsFound = 0;
|
|
362
|
-
this._totalEntries = 0;
|
|
363
|
-
for (const obj of (await res).objects) {
|
|
364
|
-
const pkg = obj.package;
|
|
365
|
-
if (pkg.keywords.indexOf('deprecated') >= 0) {
|
|
366
|
-
continue;
|
|
367
|
-
}
|
|
368
|
-
this._totalEntries = this._totalEntries + 1;
|
|
369
|
-
const isblockedExtensions = this.isListed(pkg.name, this._blockedExtensionsArray);
|
|
370
|
-
if (isblockedExtensions) {
|
|
371
|
-
this._totalblockedExtensionsFound =
|
|
372
|
-
this._totalblockedExtensionsFound + 1;
|
|
373
|
-
}
|
|
374
|
-
const isallowedExtensions = this.isListed(pkg.name, this._allowedExtensionsArray);
|
|
375
|
-
if (isallowedExtensions) {
|
|
376
|
-
this._totalallowedExtensionsFound =
|
|
377
|
-
this._totalallowedExtensionsFound + 1;
|
|
378
|
-
}
|
|
379
|
-
entries[pkg.name] = {
|
|
380
|
-
name: pkg.name,
|
|
381
|
-
description: pkg.description,
|
|
382
|
-
url: 'homepage' in pkg.links
|
|
383
|
-
? pkg.links.homepage
|
|
384
|
-
: 'repository' in pkg.links
|
|
385
|
-
? pkg.links.repository
|
|
386
|
-
: pkg.links.npm,
|
|
387
|
-
installed: false,
|
|
388
|
-
enabled: false,
|
|
389
|
-
status: null,
|
|
390
|
-
latest_version: pkg.version,
|
|
391
|
-
installed_version: '',
|
|
392
|
-
blockedExtensionsEntry: isblockedExtensions,
|
|
393
|
-
allowedExtensionsEntry: isallowedExtensions,
|
|
394
|
-
pkg_type: 'source',
|
|
395
|
-
install: undefined
|
|
396
|
-
};
|
|
397
|
-
}
|
|
398
|
-
return entries;
|
|
399
|
-
}
|
|
400
|
-
/**
|
|
401
|
-
* Translate installed extensions information from the server into entries.
|
|
402
|
-
*
|
|
403
|
-
* @param res Promise to the server reply data.
|
|
404
|
-
*/
|
|
405
|
-
async translateInstalled(res) {
|
|
406
|
-
const promises = [];
|
|
407
|
-
const entries = {};
|
|
408
|
-
for (const pkg of await res) {
|
|
409
|
-
promises.push(res.then(info => {
|
|
410
|
-
var _a, _b, _c;
|
|
411
|
-
entries[pkg.name] = {
|
|
412
|
-
name: pkg.name,
|
|
413
|
-
description: pkg.description,
|
|
414
|
-
url: pkg.url,
|
|
415
|
-
installed: pkg.installed !== false,
|
|
416
|
-
enabled: pkg.enabled,
|
|
417
|
-
status: pkg.status,
|
|
418
|
-
latest_version: pkg.latest_version,
|
|
419
|
-
installed_version: pkg.installed_version,
|
|
420
|
-
blockedExtensionsEntry: this.isListed(pkg.name, this._blockedExtensionsArray),
|
|
421
|
-
allowedExtensionsEntry: this.isListed(pkg.name, this._allowedExtensionsArray),
|
|
422
|
-
pkg_type: pkg.pkg_type,
|
|
423
|
-
install: {
|
|
424
|
-
packageManager: (_a = pkg.install) === null || _a === void 0 ? void 0 : _a.packageManager,
|
|
425
|
-
packageName: (_b = pkg.install) === null || _b === void 0 ? void 0 : _b.packageName,
|
|
426
|
-
uninstallInstructions: (_c = pkg.install) === null || _c === void 0 ? void 0 : _c.uninstallInstructions
|
|
427
|
-
}
|
|
428
|
-
};
|
|
429
|
-
}));
|
|
218
|
+
catch (reason) {
|
|
219
|
+
this.installedError = reason.toString();
|
|
430
220
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
}
|
|
435
|
-
isListed(name, listArray) {
|
|
436
|
-
let entry = undefined;
|
|
437
|
-
listArray.forEach((listEntry) => {
|
|
438
|
-
var _a;
|
|
439
|
-
if (listEntry.regexp && ((_a = listEntry.regexp) === null || _a === void 0 ? void 0 : _a.test(name))) {
|
|
440
|
-
entry = listEntry;
|
|
441
|
-
}
|
|
442
|
-
});
|
|
443
|
-
return entry;
|
|
444
|
-
}
|
|
445
|
-
/**
|
|
446
|
-
* Make a request to the server for info about its installed extensions.
|
|
447
|
-
*/
|
|
448
|
-
fetchInstalled(refreshInstalled = false) {
|
|
449
|
-
const url = new URL(EXTENSION_API_PATH, this.serverConnectionSettings.baseUrl);
|
|
450
|
-
if (refreshInstalled) {
|
|
451
|
-
url.searchParams.append('refresh', '1');
|
|
221
|
+
finally {
|
|
222
|
+
this._isLoadingInstalledExtensions = false;
|
|
223
|
+
this.stateChanged.emit();
|
|
452
224
|
}
|
|
453
|
-
const request = ServerConnection.makeRequest(url.toString(), {}, this.serverConnectionSettings).then(response => {
|
|
454
|
-
Private.handleError(response);
|
|
455
|
-
return response.json();
|
|
456
|
-
});
|
|
457
|
-
request.then(() => {
|
|
458
|
-
this.serverConnectionError = null;
|
|
459
|
-
}, reason => {
|
|
460
|
-
this.serverConnectionError = reason.toString();
|
|
461
|
-
});
|
|
462
|
-
return request;
|
|
463
225
|
}
|
|
464
226
|
/**
|
|
465
227
|
* Search with current query.
|
|
466
228
|
*
|
|
467
229
|
* Sets searchError and totalEntries as appropriate.
|
|
468
230
|
*
|
|
469
|
-
* @returns
|
|
231
|
+
* @returns The extensions matching the current query.
|
|
470
232
|
*/
|
|
471
|
-
async
|
|
472
|
-
|
|
473
|
-
|
|
233
|
+
async search(force = false) {
|
|
234
|
+
var _a, _b;
|
|
235
|
+
if (!this.isDisclaimed) {
|
|
236
|
+
return Promise.reject('Installation warning is not disclaimed.');
|
|
474
237
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
let searchMap;
|
|
238
|
+
this.searchError = null;
|
|
239
|
+
this._isSearching = true;
|
|
240
|
+
this.stateChanged.emit();
|
|
479
241
|
try {
|
|
480
|
-
|
|
481
|
-
|
|
242
|
+
const [extensions, links] = await Private.requestAPI({
|
|
243
|
+
query: (_a = this.query) !== null && _a !== void 0 ? _a : '',
|
|
244
|
+
page: this.page,
|
|
245
|
+
per_page: this.pagination,
|
|
246
|
+
refresh: force ? 1 : 0
|
|
247
|
+
});
|
|
248
|
+
const lastURL = links['last'];
|
|
249
|
+
if (lastURL) {
|
|
250
|
+
const lastPage = URLExt.queryStringToObject((_b = URLExt.parse(lastURL).search) !== null && _b !== void 0 ? _b : '')['page'];
|
|
251
|
+
if (lastPage) {
|
|
252
|
+
this._lastPage = parseInt(lastPage, 10);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const installedNames = this._installed.map(pkg => pkg.name);
|
|
256
|
+
this._lastSearchResult = extensions
|
|
257
|
+
.filter(pkg => !installedNames.includes(pkg.name))
|
|
258
|
+
.sort(Private.comparator);
|
|
482
259
|
}
|
|
483
260
|
catch (reason) {
|
|
484
|
-
searchMap = {};
|
|
485
261
|
this.searchError = reason.toString();
|
|
486
262
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
* Query the installed extensions.
|
|
491
|
-
*
|
|
492
|
-
* Sets installedError as appropriate.
|
|
493
|
-
*
|
|
494
|
-
* @returns {Promise<{ [key: string]: IEntry; }>} A map of installed extensions.
|
|
495
|
-
*/
|
|
496
|
-
async queryInstalled(refreshInstalled) {
|
|
497
|
-
let installedMap;
|
|
498
|
-
try {
|
|
499
|
-
installedMap = await this.translateInstalled(this.fetchInstalled(refreshInstalled));
|
|
500
|
-
this.installedError = null;
|
|
501
|
-
}
|
|
502
|
-
catch (reason) {
|
|
503
|
-
installedMap = {};
|
|
504
|
-
this.installedError = reason.toString();
|
|
263
|
+
finally {
|
|
264
|
+
this._isSearching = false;
|
|
265
|
+
this.stateChanged.emit();
|
|
505
266
|
}
|
|
506
|
-
return installedMap;
|
|
507
267
|
}
|
|
508
268
|
/**
|
|
509
269
|
* Update the current model.
|
|
510
270
|
*
|
|
511
|
-
* This will query the
|
|
271
|
+
* This will query the packages repository, and the notebook server.
|
|
512
272
|
*
|
|
513
273
|
* Emits the `stateChanged` signal on successful completion.
|
|
514
274
|
*/
|
|
515
|
-
async update(
|
|
516
|
-
if (
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
]);
|
|
521
|
-
// Map results to attributes:
|
|
522
|
-
const installed = [];
|
|
523
|
-
for (const key of Object.keys(installedMap)) {
|
|
524
|
-
installed.push(installedMap[key]);
|
|
525
|
-
}
|
|
526
|
-
this._installed = installed.sort(Private.comparator);
|
|
527
|
-
const searchResult = [];
|
|
528
|
-
for (const key of Object.keys(searchMap)) {
|
|
529
|
-
// Filter out installed entries from search results:
|
|
530
|
-
if (installedMap[key] === undefined) {
|
|
531
|
-
searchResult.push(searchMap[key]);
|
|
532
|
-
}
|
|
533
|
-
else {
|
|
534
|
-
searchResult.push(installedMap[key]);
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
this._searchResult = searchResult.sort(Private.comparator);
|
|
275
|
+
async update(force = false) {
|
|
276
|
+
if (this.isDisclaimed) {
|
|
277
|
+
// First refresh the installed list - so the search results are correctly filtered
|
|
278
|
+
await this.refreshInstalled(force);
|
|
279
|
+
await this.search();
|
|
538
280
|
}
|
|
539
|
-
// Signal updated state
|
|
540
|
-
this.stateChanged.emit(undefined);
|
|
541
281
|
}
|
|
542
282
|
/**
|
|
543
283
|
* Send a request to the server to perform an action on an extension.
|
|
@@ -545,34 +285,54 @@ export class ListModel extends VDomModel {
|
|
|
545
285
|
* @param action A valid action to perform.
|
|
546
286
|
* @param entry The extension to perform the action on.
|
|
547
287
|
*/
|
|
548
|
-
|
|
549
|
-
const
|
|
550
|
-
const request = {
|
|
288
|
+
performAction(action, entry) {
|
|
289
|
+
const actionRequest = Private.requestAPI({}, {
|
|
551
290
|
method: 'POST',
|
|
552
291
|
body: JSON.stringify({
|
|
553
292
|
cmd: action,
|
|
554
293
|
extension_name: entry.name
|
|
555
294
|
})
|
|
556
|
-
};
|
|
557
|
-
const completed = ServerConnection.makeRequest(url.toString(), request, this.serverConnectionSettings).then(response => {
|
|
558
|
-
Private.handleError(response);
|
|
559
|
-
this.triggerBuildCheck();
|
|
560
|
-
return response.json();
|
|
561
295
|
});
|
|
562
|
-
|
|
563
|
-
|
|
296
|
+
actionRequest.then(([reply]) => {
|
|
297
|
+
const trans = this.translator.load('jupyterlab');
|
|
298
|
+
if (reply.needs_restart.includes('server')) {
|
|
299
|
+
void showDialog({
|
|
300
|
+
title: trans.__('Information'),
|
|
301
|
+
body: trans.__('You will need to restart JupyterLab to apply the changes.'),
|
|
302
|
+
buttons: [Dialog.okButton({ label: trans.__('Ok') })]
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
const followUps = [];
|
|
307
|
+
if (reply.needs_restart.includes('frontend')) {
|
|
308
|
+
followUps.push(
|
|
309
|
+
// @ts-expect-error isElectron is not a standard attribute
|
|
310
|
+
window.isElectron
|
|
311
|
+
? trans.__('reload JupyterLab')
|
|
312
|
+
: trans.__('refresh the web page'));
|
|
313
|
+
}
|
|
314
|
+
if (reply.needs_restart.includes('kernel')) {
|
|
315
|
+
followUps.push(trans.__('install the extension in all kernels and restart them'));
|
|
316
|
+
}
|
|
317
|
+
void showDialog({
|
|
318
|
+
title: trans.__('Information'),
|
|
319
|
+
body: trans.__('You will need to %1 to apply the changes.', followUps.join(trans.__(' and '))),
|
|
320
|
+
buttons: [Dialog.okButton({ label: trans.__('Ok') })]
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
this.actionError = null;
|
|
564
324
|
}, reason => {
|
|
565
|
-
this.
|
|
325
|
+
this.actionError = reason.toString();
|
|
566
326
|
});
|
|
567
|
-
this.
|
|
568
|
-
return
|
|
327
|
+
this.addPendingAction(actionRequest);
|
|
328
|
+
return actionRequest.then(([reply]) => reply);
|
|
569
329
|
}
|
|
570
330
|
/**
|
|
571
331
|
* Add a pending action.
|
|
572
332
|
*
|
|
573
333
|
* @param pending A promise that resolves when the action is completed.
|
|
574
334
|
*/
|
|
575
|
-
|
|
335
|
+
addPendingAction(pending) {
|
|
576
336
|
// Add to pending actions collection
|
|
577
337
|
this._pendingActions.push(pending);
|
|
578
338
|
// Ensure action is removed when resolved
|
|
@@ -586,7 +346,6 @@ export class ListModel extends VDomModel {
|
|
|
586
346
|
this.stateChanged.emit(undefined);
|
|
587
347
|
}
|
|
588
348
|
}
|
|
589
|
-
let _isDisclaimed = false;
|
|
590
349
|
/**
|
|
591
350
|
* ListModel statics.
|
|
592
351
|
*/
|
|
@@ -603,14 +362,6 @@ let _isDisclaimed = false;
|
|
|
603
362
|
return semver.lt(entry.installed_version, entry.latest_version);
|
|
604
363
|
}
|
|
605
364
|
ListModel.entryHasUpdate = entryHasUpdate;
|
|
606
|
-
function isDisclaimed() {
|
|
607
|
-
return _isDisclaimed;
|
|
608
|
-
}
|
|
609
|
-
ListModel.isDisclaimed = isDisclaimed;
|
|
610
|
-
function toogleDisclaimed() {
|
|
611
|
-
_isDisclaimed = !_isDisclaimed;
|
|
612
|
-
}
|
|
613
|
-
ListModel.toogleDisclaimed = toogleDisclaimed;
|
|
614
365
|
})(ListModel || (ListModel = {}));
|
|
615
366
|
/**
|
|
616
367
|
* A namespace for private functionality.
|
|
@@ -624,67 +375,52 @@ var Private;
|
|
|
624
375
|
if (a.name === b.name) {
|
|
625
376
|
return 0;
|
|
626
377
|
}
|
|
627
|
-
const testA = isJupyterOrg(a.name);
|
|
628
|
-
const testB = isJupyterOrg(b.name);
|
|
629
|
-
if (testA === testB) {
|
|
630
|
-
// Retain sort-order from API
|
|
631
|
-
return 0;
|
|
632
|
-
}
|
|
633
|
-
else if (testA && !testB) {
|
|
634
|
-
return -1;
|
|
635
|
-
}
|
|
636
378
|
else {
|
|
637
|
-
return 1;
|
|
379
|
+
return a.name > b.name ? 1 : -1;
|
|
638
380
|
}
|
|
639
381
|
}
|
|
640
382
|
Private.comparator = comparator;
|
|
383
|
+
const LINK_PARSER = /<([^>]+)>; rel="([^"]+)",?/g;
|
|
641
384
|
/**
|
|
642
|
-
*
|
|
385
|
+
* Call the API extension
|
|
643
386
|
*
|
|
644
|
-
* @param
|
|
645
|
-
* @param
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
const
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
387
|
+
* @param queryArgs Query arguments
|
|
388
|
+
* @param init Initial values for the request
|
|
389
|
+
* @returns The response body interpreted as JSON and the response link header
|
|
390
|
+
*/
|
|
391
|
+
async function requestAPI(queryArgs = {}, init = {}) {
|
|
392
|
+
var _a;
|
|
393
|
+
// Make request to Jupyter API
|
|
394
|
+
const settings = ServerConnection.makeSettings();
|
|
395
|
+
const requestUrl = URLExt.join(settings.baseUrl, EXTENSION_API_PATH // API Namespace
|
|
396
|
+
);
|
|
397
|
+
let response;
|
|
398
|
+
try {
|
|
399
|
+
response = await ServerConnection.makeRequest(requestUrl + URLExt.objectToQueryString(queryArgs), init, settings);
|
|
656
400
|
}
|
|
657
|
-
|
|
658
|
-
|
|
401
|
+
catch (error) {
|
|
402
|
+
throw new ServerConnection.NetworkError(error);
|
|
659
403
|
}
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
match = reLang.test(spec.language);
|
|
404
|
+
let data = await response.text();
|
|
405
|
+
if (data.length > 0) {
|
|
406
|
+
try {
|
|
407
|
+
data = JSON.parse(data);
|
|
665
408
|
}
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
}
|
|
669
|
-
if (match) {
|
|
670
|
-
matches.push(spec);
|
|
671
|
-
continue;
|
|
409
|
+
catch (error) {
|
|
410
|
+
console.log('Not a JSON response body.', response);
|
|
672
411
|
}
|
|
673
412
|
}
|
|
674
|
-
return matches;
|
|
675
|
-
}
|
|
676
|
-
Private.matchSpecs = matchSpecs;
|
|
677
|
-
/**
|
|
678
|
-
* Convert a response to an exception on error.
|
|
679
|
-
*
|
|
680
|
-
* @param response The response to inspect.
|
|
681
|
-
*/
|
|
682
|
-
function handleError(response) {
|
|
683
413
|
if (!response.ok) {
|
|
684
|
-
throw new
|
|
414
|
+
throw new ServerConnection.ResponseError(response, data.message || data);
|
|
415
|
+
}
|
|
416
|
+
const link = (_a = response.headers.get('Link')) !== null && _a !== void 0 ? _a : '';
|
|
417
|
+
const links = {};
|
|
418
|
+
let match = null;
|
|
419
|
+
while ((match = LINK_PARSER.exec(link)) !== null) {
|
|
420
|
+
links[match[2]] = match[1];
|
|
685
421
|
}
|
|
686
|
-
return
|
|
422
|
+
return [data, links];
|
|
687
423
|
}
|
|
688
|
-
Private.
|
|
424
|
+
Private.requestAPI = requestAPI;
|
|
689
425
|
})(Private || (Private = {}));
|
|
690
426
|
//# sourceMappingURL=model.js.map
|