diff --git a/src/common/access_control.py b/src/common/access_control.py
index a486b559..8b5ae075 100644
--- a/src/common/access_control.py
+++ b/src/common/access_control.py
@@ -92,14 +92,10 @@ def user_file_visibility_q(user: AbstractBaseUser) -> Q:
def can_edit_user_file(user_file: Any, user: AbstractBaseUser) -> bool:
- """Return whether the user may edit/delete a UserFile."""
- if not user or not user.is_authenticated:
- return False
+ """Return whether the user may mutate a UserFile.
- return (
- user_file.user_id == user.id
- or user_file.institutions.filter(
- users=user,
- institutionadministration__is_institution_admin=True,
- ).exists()
- )
+ Institution membership controls visibility, but it does not grant
+ ownership or mutation permissions. Dataset edits, deletion, visibility
+ changes, and sharing changes are therefore restricted to the owner.
+ """
+ return bool(user and user.is_authenticated and user_file.user_id == user.id)
diff --git a/src/frontend/static/frontend/src/components/biomarkers/SharedInstitutionsBiomarker.tsx b/src/frontend/static/frontend/src/components/biomarkers/SharedInstitutionsBiomarker.tsx
index ff84cac4..98f9bf6a 100644
--- a/src/frontend/static/frontend/src/components/biomarkers/SharedInstitutionsBiomarker.tsx
+++ b/src/frontend/static/frontend/src/components/biomarkers/SharedInstitutionsBiomarker.tsx
@@ -1,12 +1,10 @@
import React, { useContext, useEffect, useRef, useState } from 'react'
-import { Button, Divider, Grid, GridColumn, Icon, List, ListContent, ListHeader, ListItem, Modal, ModalContent, ModalHeader, Segment, Select, Table } from 'semantic-ui-react'
import ky from 'ky'
-import { DjangoInstitutionUserLimited } from '../../utils/django_interfaces'
-import { PaginatedTable } from '../common/PaginatedTable'
-import { TableCellWithTitle } from '../common/TableCellWithTitle'
import { CurrentUserContext } from '../Base'
import { SemanticListItem } from '../../utils/interfaces'
import { getDjangoHeader } from '../../utils/util_functions'
+import { useIntl } from 'react-intl'
+import { SharedInstitution, SharedInstitutionsModal } from '../common/SharedInstitutionsModal'
declare const urlGetUsersCandidatesLimitedBiomarker: string
declare const urlGetInstitutionsNonInExperimentBiomarker: string
@@ -26,36 +24,6 @@ interface SharedInstitutionsBiomarkerProps extends SharedInstitutionsBiomarkerPr
handleChangeConfirmModalState: (setOption: boolean, headerText: string, contentText: string, onConfirm: () => void) => void,
}
-interface InstitutionUserListProps {
- institutionName: string,
- institutionId: number,
-}
-
-const InstitutionUserList = (props: InstitutionUserListProps) => {
- return (
-
-
- headerTitle={props.institutionName + ' users'}
- headers={[
- { name: 'User name', serverCodeToSort: 'user__username' as any, width: 3 }
- ]}
- showSearchInput
- searchLabel='User name'
- searchPlaceholder='Search by User name'
- urlToRetrieveData={urlGetUsersCandidatesLimitedBiomarker + '/' + props.institutionId + '/'}
- updateWSKey='update_user_for_institution'
- mapFunction={(userCandidate: DjangoInstitutionUserLimited) => {
- return (
-
-
-
- )
- }}
- />
-
- )
-}
-
/**
* Modal that shows institutions shared to a biomarker
* @param props Component props
@@ -64,11 +32,12 @@ const InstitutionUserList = (props: InstitutionUserListProps) => {
export const SharedInstitutionsBiomarker = (props: SharedInstitutionsBiomarkerProps) => {
const [activeInstitution, setActiveInstitution] = useState<{ id: number, name: string }>({ id: 0, name: '' })
const [listOfInstitutionNonPart, setListOfInstitutionNonPart] = useState([])
- const [institutionIdToAdd, setInstitutionIdToAdd] = useState(0)
+ const [institutionIdToAdd, setInstitutionIdToAdd] = useState(null)
const abortController = useRef(new AbortController())
const [institutionList, setInstitutionList] = useState<{ id: number, name: string }[]>([])
const [isLoadingInstitution, setIsLoadingInstitution] = useState(false)
const currentUser = useContext(CurrentUserContext)
+ const intl = useIntl()
/**
* Function to search institutions that are not in experiment.
@@ -120,11 +89,12 @@ export const SharedInstitutionsBiomarker = (props: SharedInstitutionsBiomarkerPr
setIsLoadingInstitution(true)
const myHeaders = getDjangoHeader()
const body = {
- institutionId: institutionIdToAdd,
+ institutionId: Number(institutionIdToAdd),
biomarkerId: props.biomarkerId
}
ky.post(urlShareBiomarkerToInstitution, { headers: myHeaders, signal: abortController.current.signal, json: body }).then((response) => {
response.json().then(() => {
+ setInstitutionIdToAdd(null)
InstitutionsistNonInExperiment()
usersListInstitution()
}).catch((err) => {
@@ -165,92 +135,51 @@ export const SharedInstitutionsBiomarker = (props: SharedInstitutionsBiomarkerPr
}
useEffect(() => {
- if (props.biomarkerId) {
+ if (props.isOpen && props.biomarkerId) {
+ setInstitutionIdToAdd(null)
InstitutionsistNonInExperiment()
usersListInstitution()
}
- }, [props.biomarkerId])
+ }, [props.biomarkerId, props.isOpen])
- return (
- props.handleClose()}
- open={props.isOpen}
- closeIcon={ props.handleClose()} />}
- style={{ width: '60%', maxWidth: '1000px' }}
- >
- Shared institutions
-
- {
- props.user.id === currentUser?.id &&
- (
- <>
-
-
+ return (
+ intl.formatMessage(
+ { id: 'sharedInstitutionsBiomarker.users.title' },
+ { institutionName }
+ )}
+ userNameLabel={intl.formatMessage({ id: 'sharedInstitutionsBiomarker.users.column' })}
+ searchUserNamePlaceholder={intl.formatMessage({ id: 'sharedInstitutionsBiomarker.users.searchPlaceholder' })}
+ removeInstitutionTitle={intl.formatMessage({ id: 'sharedInstitutionsBiomarker.removeInstitution' })}
+ isLoading={isLoadingInstitution}
+ canManage={canManage}
+ onClose={props.handleClose}
+ onInstitutionToAddChange={setInstitutionIdToAdd}
+ onAddInstitution={() => props.handleChangeConfirmModalState(
+ true,
+ intl.formatMessage({ id: 'sharedInstitutionsBiomarker.confirm.share.header' }),
+ intl.formatMessage({ id: 'sharedInstitutionsBiomarker.confirm.share.content' }),
+ handleAddInstitution
+ )}
+ onSelectInstitution={setActiveInstitution}
+ onRemoveInstitution={(institutionId) => props.handleChangeConfirmModalState(
+ true,
+ intl.formatMessage({ id: 'sharedInstitutionsBiomarker.confirm.stopShare.header' }),
+ intl.formatMessage({ id: 'sharedInstitutionsBiomarker.confirm.stopShare.content' }),
+ () => handleRemoveInstitution(institutionId)
+ )}
+ />
)
}
diff --git a/src/frontend/static/frontend/src/components/common/SharedInstitutionsModal.tsx b/src/frontend/static/frontend/src/components/common/SharedInstitutionsModal.tsx
new file mode 100644
index 00000000..4cacf1b4
--- /dev/null
+++ b/src/frontend/static/frontend/src/components/common/SharedInstitutionsModal.tsx
@@ -0,0 +1,164 @@
+import React, { ReactNode } from 'react'
+import { Button, Grid, GridColumn, Icon, List, ListContent, ListHeader, ListItem, Modal, ModalContent, ModalHeader, Segment, Select, Table } from 'semantic-ui-react'
+import { DjangoInstitutionUserLimited } from '../../utils/django_interfaces'
+import { SemanticListItem } from '../../utils/interfaces'
+import { PaginatedTable } from './PaginatedTable'
+import '../../css/shared-institutions.css'
+
+export interface SharedInstitution {
+ id: number,
+ name: string
+}
+
+interface SharedInstitutionsModalProps {
+ isOpen: boolean,
+ title: ReactNode,
+ selectPlaceholder: string,
+ addButtonText: string,
+ availableInstitutions: SemanticListItem[],
+ selectedInstitutionToAdd: string | null,
+ sharedInstitutions: SharedInstitution[],
+ selectedInstitution: SharedInstitution | null,
+ usersUrl: string,
+ usersHeaderTitle: (institutionName: string) => string,
+ userNameLabel: string,
+ searchUserNamePlaceholder: string,
+ removeInstitutionTitle: string,
+ isLoading: boolean,
+ canManage: boolean,
+ onClose: VoidFunction,
+ onInstitutionToAddChange: (value: string | null) => void,
+ onAddInstitution: VoidFunction,
+ onSelectInstitution: (institution: SharedInstitution) => void,
+ onRemoveInstitution: (institutionId: number) => void,
+}
+
+interface InstitutionUserListProps {
+ institutionName: string,
+ institutionId: number,
+ usersUrl: string,
+ usersHeaderTitle: (institutionName: string) => string,
+ userNameLabel: string,
+ searchUserNamePlaceholder: string,
+}
+
+const InstitutionUserList = (props: InstitutionUserListProps) => {
+ return (
+
+
+ key={props.institutionId}
+ headerTitle={props.usersHeaderTitle(props.institutionName)}
+ headers={[
+ { name: props.userNameLabel, serverCodeToSort: 'user__username' as any, width: 3 }
+ ]}
+ showSearchInput
+ searchLabel={props.userNameLabel}
+ searchPlaceholder={props.searchUserNamePlaceholder}
+ urlToRetrieveData={`${props.usersUrl}/${props.institutionId}/`}
+ updateWSKey='update_user_for_institution'
+ mapFunction={(userCandidate: DjangoInstitutionUserLimited) => {
+ return (
+
+
+
+ {userCandidate.user.username}
+
+
+ )
+ }}
+ />
+
+ )
+}
+
+export const SharedInstitutionsModal = (props: SharedInstitutionsModalProps) => {
+ return (
+ }
+ style={{ width: '70%', maxWidth: '1100px' }}
+ >
+ {props.title}
+
+ {props.canManage && (
+
+
+ )}
+
+
+
+
+
+
+ {props.sharedInstitutions.map(institution => (
+
props.onSelectInstitution(institution)}
+ >
+
+
+
+ {institution.name}
+
+ {props.canManage && (
+ {
+ event.stopPropagation()
+ props.onRemoveInstitution(institution.id)
+ }}
+ />
+ )}
+
+
+ ))}
+
+
+
+
+ {props.selectedInstitution
+ ? (
+
+ )
+ : null}
+
+
+
+
+
+ )
+}
diff --git a/src/frontend/static/frontend/src/components/files-manager/FilesManager.tsx b/src/frontend/static/frontend/src/components/files-manager/FilesManager.tsx
index 9b3827dc..858867f1 100644
--- a/src/frontend/static/frontend/src/components/files-manager/FilesManager.tsx
+++ b/src/frontend/static/frontend/src/components/files-manager/FilesManager.tsx
@@ -15,7 +15,7 @@ import { PopupIcons } from '../common/PopupIcons'
import { SwitchPublicButton } from '../common/SwitchPublicButton'
import { useIntl, IntlShape } from 'react-intl'
import { DeleteButton } from '../common/DeleteButton'
-import { getTissueDropdownOptions, getTissueIds, TissueLabels } from '../common/TissueLabels'
+import { getTissueDropdownOptions, getTissueIds } from '../common/TissueLabels'
import { SharedInstitutionsUserFile } from './SharedInstitutionsUserFile'
/** Structure returned from the chunk upload service. */
@@ -31,10 +31,9 @@ declare const urlUserFilesCRUD: string
declare const urlUserInstitutions: string
declare const urlTissuesCRUD: string
declare const urlChunkUpload: string
-declare const urlChunkUploadComplete: string
-declare const downloadFileURL: string
-declare const downloadFileHeaders: string
-declare const currentUserId: string
+declare const urlChunkUploadComplete: string
+declare const downloadFileURL: string
+declare const downloadFileHeaders: string
/**
* New File Form fields
@@ -843,35 +842,27 @@ class FilesManager extends React.Component
{getFileTypeName(userFileRow.file_type)}
-
- {userFileRow.institutions.length > 0 && (
- i.name).join(', ') }
- )}
- />
- )}
-
-
- }
- tagType={TagType.FILE}
- onTagSelect={(tagId) => this.updateUserFileTag(userFileRow, tagId)}
- onTagCreated={() => this.getUserTags()}
- onTagEdited={() => this.getUserTags()}
- onTagDeleted={(deletedTagId) => {
- if (this.state.newFile.newTag === deletedTagId) {
- this.handleAddFileInputsChange('newTag', null)
- }
-
- this.getUserTags()
- }}
- />
-
+
+ {userFileRow.is_owner
+ ? (
+ }
+ tagType={TagType.FILE}
+ onTagSelect={(tagId) => this.updateUserFileTag(userFileRow, tagId)}
+ onTagCreated={() => this.getUserTags()}
+ onTagEdited={() => this.getUserTags()}
+ onTagDeleted={(deletedTagId) => {
+ if (this.state.newFile.newTag === deletedTagId) {
+ this.handleAddFileInputsChange('newTag', null)
+ }
+
+ this.getUserTags()
+ }}
+ />
+ )
+ : }
+
{(this.state.sharedInstitutionsByFile[userFileRow.id as number] ?? userFileRow.institutions).length > 0
? (
@@ -886,9 +877,7 @@ class FilesManager extends React.Component
)
: '-'}
-
-
-
+
{
userFileRow.is_public
? (
@@ -918,7 +907,7 @@ class FilesManager extends React.Component
{ columnName: userFileRow.column_used_as_index }
)}
/>
- {Number(currentUserId) === userFileRow.user.id && (
+ {userFileRow.is_owner && (
onClick={() => this.openShareInstitutions(userFileRow)}
/>
)}
- {/* Users can modify or delete own files or the ones which belongs to an
- Institution which the user is admin of */}
- {userFileRow.is_private_or_institution_admin && (
+ {/* Only the dataset owner can modify or delete it. */}
+ {userFileRow.is_owner && (
<>
{/* Shows a edit button if specified */}
{
const abortController = useRef(new AbortController())
const [availableInstitutions, setAvailableInstitutions] = useState([])
const [sharedInstitutions, setSharedInstitutions] = useState([])
- const [institutionIdToAdd, setInstitutionIdToAdd] = useState(0)
+ const [institutionIdToAdd, setInstitutionIdToAdd] = useState(null)
+ const [activeInstitution, setActiveInstitution] = useState(null)
const [isLoading, setIsLoading] = useState(false)
/** Fetches the institutions available for sharing and those already shared. */
@@ -61,8 +58,10 @@ export const SharedInstitutionsUserFile = (props: Props) => {
setSharedInstitutions(shared)
props.onInstitutionsChange(shared)
- if (!shared.some(institution => institution.id === institutionIdToAdd)) {
- setInstitutionIdToAdd(0)
+ setInstitutionIdToAdd(null)
+
+ if (!shared.some(institution => institution.id === activeInstitution?.id)) {
+ setActiveInstitution(shared[0] ?? null)
}
}).catch((err) => {
if (!abortController.current.signal.aborted) {
@@ -82,7 +81,7 @@ export const SharedInstitutionsUserFile = (props: Props) => {
headers: getDjangoHeader(),
signal: abortController.current.signal,
json: {
- institutionId: institutionIdToAdd,
+ institutionId: Number(institutionIdToAdd),
userFileId: props.userFileId
}
}).then(() => loadInstitutions())
@@ -119,64 +118,37 @@ export const SharedInstitutionsUserFile = (props: Props) => {
const canManage = currentUser?.id === props.user.id
return (
- `${institutionName} ${intl.formatMessage({ id: 'sharedInstitutions.users' })}`}
+ userNameLabel={intl.formatMessage({ id: 'sharedInstitutions.userName' })}
+ searchUserNamePlaceholder={intl.formatMessage({ id: 'sharedInstitutions.searchUserName' })}
+ removeInstitutionTitle={intl.formatMessage({ id: 'files.manager.sharedInstitutions.remove' })}
+ isLoading={isLoading}
+ canManage={canManage}
onClose={props.handleClose}
- open={props.isOpen}
- closeIcon={}
- style={{ width: '45%', maxWidth: '650px' }}
- >
- {intl.formatMessage({ id: 'files.manager.sharedInstitutions.title' })}
-
- {canManage && (
-
-
- )}
-
-
- {sharedInstitutions.map(institution => (
-
-
-
- {institution.name}
-
- {canManage && (
- props.handleChangeConfirmModalState(
- true,
- intl.formatMessage({ id: 'files.manager.sharedInstitutions.removeTitle' }),
- intl.formatMessage({ id: 'files.manager.sharedInstitutions.removeConfirm' }),
- () => handleRemoveInstitution(institution.id)
- )}
- />
- )}
-
-
- ))}
-
-
-
+ onInstitutionToAddChange={setInstitutionIdToAdd}
+ onAddInstitution={() => props.handleChangeConfirmModalState(
+ true,
+ intl.formatMessage({ id: 'files.manager.sharedInstitutions.addTitle' }),
+ intl.formatMessage({ id: 'files.manager.sharedInstitutions.addConfirm' }),
+ handleAddInstitution
+ )}
+ onSelectInstitution={setActiveInstitution}
+ onRemoveInstitution={(institutionId) => props.handleChangeConfirmModalState(
+ true,
+ intl.formatMessage({ id: 'files.manager.sharedInstitutions.removeTitle' }),
+ intl.formatMessage({ id: 'files.manager.sharedInstitutions.removeConfirm' }),
+ () => handleRemoveInstitution(institutionId)
+ )}
+ />
)
}
diff --git a/src/frontend/static/frontend/src/components/pipeline/all-experiments-view/SharedInstitutions.tsx b/src/frontend/static/frontend/src/components/pipeline/all-experiments-view/SharedInstitutions.tsx
index 71d6c110..bfb0bf70 100644
--- a/src/frontend/static/frontend/src/components/pipeline/all-experiments-view/SharedInstitutions.tsx
+++ b/src/frontend/static/frontend/src/components/pipeline/all-experiments-view/SharedInstitutions.tsx
@@ -1,13 +1,10 @@
import React, { useContext, useEffect, useRef, useState } from 'react'
-import { Button, Divider, Grid, GridColumn, Icon, List, ListContent, ListHeader, ListItem, Modal, ModalContent, ModalHeader, Segment, Select, Table } from 'semantic-ui-react'
-import { PaginatedTable } from '../../common/PaginatedTable'
-import { DjangoInstitutionUserLimited } from '../../../utils/django_interfaces'
-import { TableCellWithTitle } from '../../common/TableCellWithTitle'
import { getDjangoHeader } from '../../../utils/util_functions'
import ky from 'ky'
import { SemanticListItem } from '../../../utils/interfaces'
import { CurrentUserContext } from '../../Base'
import { useIntl } from 'react-intl'
+import { SharedInstitution, SharedInstitutionsModal } from '../../common/SharedInstitutionsModal'
declare const urlGetUsersCandidatesLimited: string
declare const urlGetInstitutionsNonInExperiment: string
@@ -27,41 +24,10 @@ interface Props extends SharedInstitutionsProps {
handleChangeConfirmModalState: (setOption: boolean, headerText: string, contentText: string, onConfirm: () => void) => void,
}
-interface InstitutionUserListProps {
- institutionName: string,
- institutionId: number,
-}
-
-const InstitutionUserList = (props: InstitutionUserListProps) => {
- const intl = useIntl()
- return (
-
-
- headerTitle={`${props.institutionName} ${intl.formatMessage({ id: 'sharedInstitutions.users' })}`}
- headers={[
- { name: intl.formatMessage({ id: 'sharedInstitutions.userName' }), serverCodeToSort: 'user__username' as any, width: 3 }
- ]}
- showSearchInput
- searchLabel={intl.formatMessage({ id: 'sharedInstitutions.userName' })}
- searchPlaceholder={intl.formatMessage({ id: 'sharedInstitutions.searchUserName' })}
- urlToRetrieveData={urlGetUsersCandidatesLimited + '/' + props.institutionId + '/'}
- updateWSKey='update_user_for_institution'
- mapFunction={(userCandidate: DjangoInstitutionUserLimited) => {
- return (
-
-
-
- )
- }}
- />
-
- )
-}
-
export const SharedInstitutions = (props: Props) => {
const [activeInstitution, setActiveInstitution] = useState<{ id: number, name: string }>({ id: 0, name: '' })
const [listOfInstitutionNonPart, setListOfInstitutionNonPart] = useState([])
- const [institutionIdToAdd, setInstitutionIdToAdd] = useState(0)
+ const [institutionIdToAdd, setInstitutionIdToAdd] = useState(null)
const abortController = useRef(new AbortController())
const [institutionList, setInstitutionList] = useState<{ id: number, name: string }[]>([])
const [isLoadingInstitution, setIsLoadingInstitution] = useState(false)
@@ -118,11 +84,12 @@ export const SharedInstitutions = (props: Props) => {
setIsLoadingInstitution(true)
const myHeaders = getDjangoHeader()
const body = {
- institutionId: institutionIdToAdd,
+ institutionId: Number(institutionIdToAdd),
experimentId: props.experimentId
}
ky.post(urlShareExperimentToInstitution, { headers: myHeaders, signal: abortController.current.signal, json: body }).then((response) => {
response.json().then(() => {
+ setInstitutionIdToAdd(null)
InstitutionsistNonInExperiment()
usersListInstitution()
}).catch((err) => {
@@ -163,92 +130,48 @@ export const SharedInstitutions = (props: Props) => {
}
useEffect(() => {
- if (props.experimentId) {
+ if (props.isOpen && props.experimentId) {
+ setInstitutionIdToAdd(null)
InstitutionsistNonInExperiment()
usersListInstitution()
}
- }, [props.experimentId])
+ }, [props.experimentId, props.isOpen])
- return (
- props.handleClose()}
- open={props.isOpen}
- closeIcon={ props.handleClose()} />}
- style={{ width: '60%', maxWidth: '1000px' }}
- >
- {intl.formatMessage({ id: 'sharedInstitutions.title' })}
-
- {
- props.user.id === currentUser?.id &&
- (
- <>
-
-
+ return (
+ `${institutionName} ${intl.formatMessage({ id: 'sharedInstitutions.users' })}`}
+ userNameLabel={intl.formatMessage({ id: 'sharedInstitutions.userName' })}
+ searchUserNamePlaceholder={intl.formatMessage({ id: 'sharedInstitutions.searchUserName' })}
+ removeInstitutionTitle={intl.formatMessage({ id: 'sharedInstitutions.removeInstitution' })}
+ isLoading={isLoadingInstitution}
+ canManage={canManage}
+ onClose={props.handleClose}
+ onInstitutionToAddChange={setInstitutionIdToAdd}
+ onAddInstitution={() => props.handleChangeConfirmModalState(
+ true,
+ intl.formatMessage({ id: 'sharedInstitutions.shareExperiment' }),
+ intl.formatMessage({ id: 'sharedInstitutions.confirmShare' }),
+ handleAddInstitution
+ )}
+ onSelectInstitution={setActiveInstitution}
+ onRemoveInstitution={(institutionId) => props.handleChangeConfirmModalState(
+ true,
+ intl.formatMessage({ id: 'sharedInstitutions.stopSharing' }),
+ intl.formatMessage({ id: 'sharedInstitutions.confirmStopSharing' }),
+ () => handleRemoveInstitution(institutionId)
+ )}
+ />
)
}
diff --git a/src/frontend/static/frontend/src/css/shared-institutions.css b/src/frontend/static/frontend/src/css/shared-institutions.css
new file mode 100644
index 00000000..b5d5b685
--- /dev/null
+++ b/src/frontend/static/frontend/src/css/shared-institutions.css
@@ -0,0 +1,68 @@
+.shared-institutions-controls {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ margin-bottom: 1.5rem;
+}
+
+.shared-institutions-controls .ui.dropdown {
+ flex: 1;
+}
+
+.shared-institutions-panel {
+ margin-top: 0 !important;
+ padding: 1.5rem !important;
+}
+
+.shared-institutions-items {
+ min-height: 24rem;
+}
+
+.shared-institutions-list.ui.selection.list .list > .shared-institutions-item {
+ cursor: pointer;
+ border-left: 3px solid transparent;
+ padding: 0.75em 0.85em;
+ transition: background-color 0.15s ease, border-left-color 0.15s ease;
+}
+
+.shared-institutions-list.ui.selection.list .list > .shared-institutions-item:hover {
+ background-color: rgba(0, 0, 0, 0.04);
+}
+
+.shared-institutions-list.ui.selection.list .list > .shared-institutions-item.active {
+ background-color: rgba(33, 133, 208, 0.12);
+ border-left: 3px solid #2185d0;
+}
+
+.shared-institutions-item-content {
+ display: flex;
+ align-items: center;
+ min-height: 2.25rem;
+}
+
+.shared-institutions-item-content .content {
+ flex: 1;
+}
+
+.shared-institutions-users {
+ padding-right: 0.5rem;
+}
+
+.shared-institutions-users-column {
+ padding-left: 2.5rem !important;
+}
+
+@media (max-width: 767px) {
+ .shared-institutions-controls {
+ align-items: stretch;
+ flex-direction: column;
+ }
+
+ .shared-institutions-controls .ui.button {
+ align-self: flex-start;
+ }
+
+ .shared-institutions-users-column {
+ padding-left: 1rem !important;
+ }
+}
diff --git a/src/frontend/static/frontend/src/utils/django_interfaces.ts b/src/frontend/static/frontend/src/utils/django_interfaces.ts
index 3c7c8e66..bb08cc87 100644
--- a/src/frontend/static/frontend/src/utils/django_interfaces.ts
+++ b/src/frontend/static/frontend/src/utils/django_interfaces.ts
@@ -240,7 +240,7 @@ interface DjangoUserFile extends DjangoSimpleUserFile {
tag: DjangoTag,
upload_date?: string,
institutions: DjangoInstitutionSimple[],
- is_private_or_institution_admin: boolean,
+ is_owner: boolean,
number_of_rows: number,
number_of_samples: number,
contains_nan_values: boolean,
diff --git a/src/frontend/templates/frontend/base.html b/src/frontend/templates/frontend/base.html
index 7c8b6205..256bf224 100644
--- a/src/frontend/templates/frontend/base.html
+++ b/src/frontend/templates/frontend/base.html
@@ -6,7 +6,7 @@
- Multiomix{% block title %}{% endblock %}
+ Multiomix {% block title %}{% endblock %}
{# Favicon #}
@@ -72,4 +72,4 @@
{% block js %}{% endblock %}
-