Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e94b260
add backend functionality to exclude drive from browser
DenisaCG Jul 21, 2025
5257f5d
add frontend function for excluding drive
DenisaCG Jul 21, 2025
c6b4ac1
add backend functionality for including drive in listing
DenisaCG Jul 21, 2025
565f46a
add frontend functionality to include drive in listing
DenisaCG Jul 21, 2025
b8a9955
iterate on include drive functionality
DenisaCG Jul 21, 2025
d2f2f9a
add commands for new funcitonalities
DenisaCG Jul 21, 2025
dd328ac
add backend function to retrieve list of excluded drives
DenisaCG Jul 22, 2025
1c1e107
add frontend request for retrieving list of excluded drives
DenisaCG Jul 22, 2025
332cb5f
refactor dialog to manage listed drives
DenisaCG Jul 22, 2025
4bde19c
refactor openDriveDialogPlugin
DenisaCG Jul 22, 2025
6003dc0
remove unnecessary code
DenisaCG Jul 22, 2025
9e71fbd
fix drives region in backend
DenisaCG Jul 23, 2025
32c59f2
update dialog for managing listed drives
DenisaCG Jul 23, 2025
0ccb108
update publc drive component
DenisaCG Jul 23, 2025
7e8df91
update available drives component
DenisaCG Jul 23, 2025
45b54ef
update available drives listed component
DenisaCG Jul 23, 2025
816fc65
update selected drives component
DenisaCG Jul 23, 2025
8490a24
remove duplicate
DenisaCG Jul 23, 2025
2195ec3
iterate on public drive functionality
DenisaCG Jul 23, 2025
e5c6957
check if external drives are excluded
DenisaCG Jul 23, 2025
775489a
iterate
DenisaCG Jul 23, 2025
4a2b8b7
iterate on style
DenisaCG Jul 23, 2025
e6f9bf8
fix icons
DenisaCG Jul 23, 2025
6996e99
update command icons
DenisaCG Jul 23, 2025
a5215e9
change commands visibility
DenisaCG Jul 23, 2025
6700e66
iterate
DenisaCG Jul 23, 2025
aac52c6
fix typos
DenisaCG Jul 24, 2025
ba05f37
remove include drive command
DenisaCG Jul 24, 2025
8004f7a
rename exclude command to remove
DenisaCG Jul 24, 2025
4914bfe
add titles to buttons
DenisaCG Jul 24, 2025
e8201a5
move drives manager command to toolbar
DenisaCG Jul 24, 2025
93effd6
remove commentes
DenisaCG Jul 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion jupyter_drives/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,20 @@ class ConfigJupyterDrivesHandler(JupyterDrivesAPIHandler):
def initialize(self, logger: logging.Logger, manager: JupyterDrivesManager):
return super().initialize(logger, manager)

@tornado.web.authenticated
async def get(self):
result = self._manager.get_excluded_drives()
self.finish(json.dumps(result["data"]))

@tornado.web.authenticated
async def post(self):
body = self.get_json_body()
result = self._manager.set_listing_limit(**body)
if 'new_limit' in body:
result = self._manager.set_listing_limit(**body)
if 'exclude_drive_name' in body:
result = self._manager.exclude_drive(**body)
if 'include_drive_name' in body:
result = self._manager.include_drive(**body)
self.finish(result)

class ListJupyterDrivesHandler(JupyterDrivesAPIHandler):
Expand Down
94 changes: 78 additions & 16 deletions jupyter_drives/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ def __init__(self, config: traitlets.config.Config) -> None:
self._max_files_listed = 1025
self._drives = None
self._external_drives = {}
self._excluded_drives = set()

# instate fsspec file system
self._file_system = fsspec.filesystem(self._config.provider, asynchronous=True)
Expand Down Expand Up @@ -171,6 +172,65 @@ def set_listing_limit(self, new_limit):

return

def exclude_drive(self, exclude_drive_name):
"""Exclude drive from listing.

Args:
exclude_bucket_name: drive to exclude
"""
try:
self._excluded_drives.add(exclude_drive_name);
except Exception as e:
raise tornado.web.HTTPError(
status_code= httpx.codes.BAD_REQUEST,
reason= f"The following error occured when excluding the drive: {e}"
)

return

def get_excluded_drives(self):
"""Get list of excluded drives.

Returns:
List of excluded drives and their properties.
"""
data = []
for drive in self._excluded_drives:
try:
data.append({
"name": drive,
"region": self._config.region_name if drive not in self._content_managers else self._content_managers[drive]["location"],
"creationDate": datetime.now().isoformat(timespec='milliseconds').replace('+00:00', 'Z'),
"mounted": False if drive not in self._content_managers else True,
"provider": self._config.provider
})
except Exception as e:
raise tornado.web.HTTPError(
status_code=httpx.codes.BAD_REQUEST,
reason=f"The following error occurred when listing excluded drives: {e}",
)

response = {
"data": data
}
return response

def include_drive(self, include_drive_name):
"""Include drive in listing.

Args:
include_bucket_name: drive to include in listing
"""
try:
self._excluded_drives.remove(include_drive_name);
except Exception as e:
raise tornado.web.HTTPError(
status_code= httpx.codes.BAD_REQUEST,
reason= f"The following error occurred when including the drive: {e}"
)

return

async def list_drives(self):
"""Get list of available drives.

Expand All @@ -196,26 +256,28 @@ async def list_drives(self):
)

for result in results:
data.append(
{
"name": result.name,
"region": self._config.region_name,
"creationDate": result.extra["creation_date"],
"mounted": False if result.name not in self._content_managers else True,
"provider": self._config.provider
}
)
if result.name not in self._excluded_drives:
data.append(
{
"name": result.name,
"region": self._config.region_name if result.name not in self._content_managers else self._content_managers[result.name]["location"],
"creationDate": result.extra["creation_date"],
"mounted": False if result.name not in self._content_managers else True,
"provider": self._config.provider
}
)

if len(self._external_drives) != 0:
for drive in self._external_drives.values():
try:
data.append({
"name": drive['url'],
"region": self._config.region_name,
"creationDate": datetime.now().isoformat(timespec='milliseconds').replace('+00:00', 'Z'),
"mounted": False if result.name not in self._content_managers else True,
"provider": self._config.provider
})
if drive['url'] not in self._excluded_drives:
data.append({
"name": drive['url'],
"region": self._config.region_name if drive['url'] not in self._content_managers else self._content_managers[drive['url']]["location"],
"creationDate": datetime.now().isoformat(timespec='milliseconds').replace('+00:00', 'Z'),
"mounted": False if result.name not in self._content_managers else True,
"provider": self._config.provider
})
except Exception as e:
raise tornado.web.HTTPError(
status_code=httpx.codes.BAD_REQUEST,
Expand Down
4 changes: 2 additions & 2 deletions schema/drives-file-browser.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
"rank": 40
},
{
"name": "new-drive",
"command": "drives:create-new-drive",
"name": "drives-manager",
"command": "drives:open-drives-dialog",
"label": "",
"rank": 5
}
Expand Down
44 changes: 43 additions & 1 deletion src/contents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ import {
copyObjects,
presignedLink,
createDrive,
getDrivesList
getDrivesList,
excludeDrive,
includeDrive
} from './requests';

let data: Contents.IModel = {
Expand Down Expand Up @@ -683,6 +685,46 @@ export class Drive implements Contents.IDrive {
return data;
}

/**
* Exclude drive from browser.
*
* @param driveName: The name of drive to exclude.
*
* @returns A promise which resolves with the contents model.
*/
async excludeDrive(driveName: string): Promise<Contents.IModel> {
data = await excludeDrive(driveName);

Contents.validateContentsModel(data);
this._fileChanged.emit({
type: 'delete',
oldValue: data,
newValue: null
});

return data;
}

/**
* Include drive in browser listing.
*
* @param driveName: The name of drive to include.
*
* @returns A promise which resolves with the contents model.
*/
async includeDrive(driveName: string): Promise<Contents.IModel> {
data = await includeDrive(driveName);

Contents.validateContentsModel(data);
this._fileChanged.emit({
type: 'new',
oldValue: null,
newValue: data
});

return data;
}

/**
* Create a checkpoint for a file.
*
Expand Down
12 changes: 12 additions & 0 deletions src/icons.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
import { LabIcon } from '@jupyterlab/ui-components';
import driveBrowserSvg from '../style/driveIconFileBrowser.svg';
import addIconSvg from '../style/addIcon.svg';
import removeIconSvg from '../style/removeIcon.svg';

export const driveBrowserIcon = new LabIcon({
name: 'jupyter-drives:drive-browser',
svgstr: driveBrowserSvg
});

export const addIcon = new LabIcon({
name: 'jupyter-drives:add-drive',
svgstr: addIconSvg
});

export const removeIcon = new LabIcon({
name: 'jupyter-drives:remove-drive',
svgstr: removeIconSvg
});
32 changes: 30 additions & 2 deletions src/plugins/driveBrowserPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import { PageConfig, PathExt } from '@jupyterlab/coreutils';
import { CommandRegistry } from '@lumino/commands';
import { Widget } from '@lumino/widgets';

import { driveBrowserIcon } from '../icons';
import { driveBrowserIcon, removeIcon } from '../icons';
import { Drive } from '../contents';
import { setListingLimit } from '../requests';
import { CommandIDs } from '../token';
Expand Down Expand Up @@ -423,7 +423,7 @@ namespace Private {
});

app.commands.addCommand(CommandIDs.addPublicDrive, {
isEnabled: () => {
isVisible: () => {
return browser.model.path === 's3:';
},
execute: async () => {
Expand Down Expand Up @@ -542,5 +542,33 @@ namespace Private {
icon: fileIcon.bindprops({ stylesheet: 'menuItem' }),
label: 'Copy Path'
});

app.commands.addCommand(CommandIDs.excludeDrive, {
isVisible: () => {
return browser.model.path === 's3:';
},
execute: async () => {
const widget = tracker.currentWidget;
if (!widget) {
return;
}
const item = widget.selectedItems().next();
if (item.done) {
return;
}

const driveName: string = item.value.name;
await drive.excludeDrive(driveName);
},
label: 'Remove Drive',
icon: removeIcon.bindprops({ stylesheet: 'menuItem' })
});

app.contextMenu.addItem({
command: CommandIDs.excludeDrive,
selector:
'#drive-file-browser.jp-SidePanel .jp-DirListing-content .jp-DirListing-item[data-isdir]',
rank: 110
});
}
}
86 changes: 24 additions & 62 deletions src/plugins/driveDialogPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ import { ITranslator } from '@jupyterlab/translation';
import { addJupyterLabThemeChangeListener } from '@jupyter/web-components';
import { Dialog, showDialog } from '@jupyterlab/apputils';

import { DriveListModel, DriveListView, IDrive } from './drivelistmanager';
import { DriveListModel, DriveListView } from './drivelistmanager';
import { driveBrowserIcon } from '../icons';
import { CommandIDs } from '../token';
import { CommandIDs, IDriveInfo } from '../token';
import { getDrivesList, getExcludedDrives } from '../requests';

export const openDriveDialogPlugin: JupyterFrontEndPlugin<void> = {
id: 'jupyter-drives:widget',
description: 'Open a dialog to select drives to be added in the filebrowser.',
description: 'Open a dialog to managed listed drives in the filebrowser.',
requires: [IFileBrowserFactory, ITranslator],
autoStart: true,
activate: (
Expand All @@ -25,68 +26,29 @@ export const openDriveDialogPlugin: JupyterFrontEndPlugin<void> = {
const { commands } = app;
const { tracker } = factory;
const trans = translator.load('jupyter_drives');
const selectedDrivesModelMap = new Map<IDrive[], DriveListModel>();
const selectedDrivesModelMap = new Map<
Partial<IDriveInfo>[],
DriveListModel
>();

let selectedDrives: IDrive[] = [
{
name: 'CoconutDrive',
url: '/coconut/url'
}
];

const availableDrives: IDrive[] = [
{
name: 'CoconutDrive',
url: '/coconut/url'
},
{
name: 'PearDrive',
url: '/pear/url'
},
{
name: 'StrawberryDrive',
url: '/strawberrydrive/url'
},
{
name: 'BlueberryDrive',
url: '/blueberrydrive/url'
},
{
name: '',
url: '/mydrive/url'
},
{
name: 'RaspberryDrive',
url: '/raspberrydrive/url'
},
let selectedDrives: Partial<IDriveInfo>[] = [];
getDrivesList().then((drives: IDriveInfo[]) => {
selectedDrives = drives.map((drive: IDriveInfo) => ({
name: drive.name,
region: drive.region
}));
});

{
name: 'PineAppleDrive',
url: ''
},
let availableDrives: Partial<IDriveInfo>[] = [];
getExcludedDrives().then((drives: IDriveInfo[]) => {
availableDrives = drives.map((drive: IDriveInfo) => ({
name: drive.name,
region: drive.region
}));
});

{ name: 'PomeloDrive', url: '/https://pomelodrive/url' },
{
name: 'OrangeDrive',
url: ''
},
{
name: 'TomatoDrive',
url: ''
},
{
name: '',
url: 'superDrive/url'
},
{
name: 'AvocadoDrive',
url: ''
}
];
let model = selectedDrivesModelMap.get(selectedDrives);

//const model = new DriveListModel(availableDrives, selectedDrives);

commands.addCommand(CommandIDs.openDrivesDialog, {
execute: args => {
const widget = tracker.currentWidget;
Expand All @@ -109,8 +71,8 @@ export const openDriveDialogPlugin: JupyterFrontEndPlugin<void> = {
},

icon: driveBrowserIcon.bindprops({ stylesheet: 'menuItem' }),
caption: trans.__('Add drives to filebrowser.'),
label: trans.__('Add Drives To Filebrowser')
caption: trans.__('Manage drives listed in filebrowser.'),
label: trans.__('Manage listed drives')
});
}
};
Loading
Loading