diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 9d49130458..ef7c799c76 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -12,7 +12,7 @@ "args": { // To ensure that the group ID for the docker group in the container // matches the group ID on the host, add this to your .bash_profile on the host - // export DOCKER_GROUP_ID=$(getent group docker | awk -F ":" '{ print $3 }') + // export DOCKER_GROUP_ID=$(getent group docker | awk -F ":" '{ print $3 }' ) "DOCKER_GROUP_ID": "${localEnv:DOCKER_GROUP_ID}", "INTERACTIVE": "true" } diff --git a/api_app/_version.py b/api_app/_version.py index fce337455e..2cfeb05209 100644 --- a/api_app/_version.py +++ b/api_app/_version.py @@ -1 +1 @@ -__version__ = "0.18.10" +__version__ = "0.18.11" diff --git a/api_app/core/config.py b/api_app/core/config.py index aa84020783..bab2ee3b36 100644 --- a/api_app/core/config.py +++ b/api_app/core/config.py @@ -65,6 +65,6 @@ API_AUDIENCE: str = config("API_AUDIENCE", default=API_CLIENT_ID) -AIRLOCK_SAS_TOKEN_EXPIRY_PERIOD_IN_HOURS: int = config("AIRLOCK_SAS_TOKEN_EXPIRY_PERIOD_IN_HOURS", default=1) +AIRLOCK_SAS_TOKEN_EXPIRY_PERIOD_IN_HOURS: int = config("AIRLOCK_SAS_TOKEN_EXPIRY_PERIOD_IN_HOURS", default=48) API_ROOT_SCOPE: str = f"api://{API_CLIENT_ID}/user_impersonation" diff --git a/core/version.txt b/core/version.txt index f8c6ac7fea..50533e307d 100644 --- a/core/version.txt +++ b/core/version.txt @@ -1 +1 @@ -__version__ = "0.9.5" +__version__ = "0.9.6" diff --git a/templates/shared_services/sonatype-nexus-vm/porter.yaml b/templates/shared_services/sonatype-nexus-vm/porter.yaml index 307bee19bc..3a43b76d2b 100644 --- a/templates/shared_services/sonatype-nexus-vm/porter.yaml +++ b/templates/shared_services/sonatype-nexus-vm/porter.yaml @@ -1,7 +1,7 @@ --- schemaVersion: 1.0.0 name: tre-shared-service-sonatype-nexus -version: 2.8.13 +version: 2.8.17 description: "A Sonatype Nexus shared service" dockerfile: Dockerfile.tmpl registry: azuretre diff --git a/templates/shared_services/sonatype-nexus-vm/scripts/nexus_realms_config.json b/templates/shared_services/sonatype-nexus-vm/scripts/nexus_realms_config.json index 51fa1053e0..eeb2530e36 100644 --- a/templates/shared_services/sonatype-nexus-vm/scripts/nexus_realms_config.json +++ b/templates/shared_services/sonatype-nexus-vm/scripts/nexus_realms_config.json @@ -1,5 +1,4 @@ [ - "NexusAuthenticatingRealm", - "NexusAuthorizingRealm", - "DockerToken" + "DockerToken", + "NexusAuthenticatingRealm" ] diff --git a/templates/shared_services/sonatype-nexus-vm/scripts/nexus_repos_config/microsoft_download_conf.json b/templates/shared_services/sonatype-nexus-vm/scripts/nexus_repos_config/microsoft_download_conf.json new file mode 100644 index 0000000000..a153c626a9 --- /dev/null +++ b/templates/shared_services/sonatype-nexus-vm/scripts/nexus_repos_config/microsoft_download_conf.json @@ -0,0 +1,32 @@ +{ + "name": "microsoft-download", + "online": true, + "storage": { + "blobStoreName": "default", + "strictContentTypeValidation": true, + "write_policy": "ALLOW" + }, + "proxy": { + "remoteUrl": "https://download.microsoft.com/download", + "contentMaxAge": 1440, + "metadataMaxAge": 1440 + }, + "negativeCache": { + "enabled": true, + "timeToLive": 1440 + }, + "httpClient": { + "blocked": false, + "autoBlock": false, + "connection": { + "retries": 0, + "userAgentSuffix": "string", + "timeout": 60, + "enableCircularRedirects": false, + "enableCookies": false, + "useTrustStore": false + } + }, + "baseType": "raw", + "repoType": "proxy" + } \ No newline at end of file diff --git a/templates/shared_services/sonatype-nexus-vm/scripts/nexus_repos_config/r_studio_download_conf.json b/templates/shared_services/sonatype-nexus-vm/scripts/nexus_repos_config/r_studio_download_conf.json new file mode 100644 index 0000000000..264deeeb36 --- /dev/null +++ b/templates/shared_services/sonatype-nexus-vm/scripts/nexus_repos_config/r_studio_download_conf.json @@ -0,0 +1,32 @@ +{ + "name": "r-studio-download", + "online": true, + "storage": { + "blobStoreName": "default", + "strictContentTypeValidation": true, + "write_policy": "ALLOW" + }, + "proxy": { + "remoteUrl": "https://download1.rstudio.org", + "contentMaxAge": 1440, + "metadataMaxAge": 1440 + }, + "negativeCache": { + "enabled": true, + "timeToLive": 1440 + }, + "httpClient": { + "blocked": false, + "autoBlock": false, + "connection": { + "retries": 0, + "userAgentSuffix": "string", + "timeout": 60, + "enableCircularRedirects": false, + "enableCookies": false, + "useTrustStore": false + } + }, + "baseType": "raw", + "repoType": "proxy" + } \ No newline at end of file diff --git a/templates/shared_services/sonatype-nexus-vm/scripts/nexus_repos_config/snapcraft_conf.json b/templates/shared_services/sonatype-nexus-vm/scripts/nexus_repos_config/snapcraft_conf.json new file mode 100644 index 0000000000..33019c0a48 --- /dev/null +++ b/templates/shared_services/sonatype-nexus-vm/scripts/nexus_repos_config/snapcraft_conf.json @@ -0,0 +1,32 @@ +{ + "name": "snapcraft", + "online": true, + "storage": { + "blobStoreName": "default", + "strictContentTypeValidation": true, + "write_policy": "ALLOW" + }, + "proxy": { + "remoteUrl": "https://snapcraftcontent.com", + "contentMaxAge": 1440, + "metadataMaxAge": 1440 + }, + "negativeCache": { + "enabled": true, + "timeToLive": 1440 + }, + "httpClient": { + "blocked": false, + "autoBlock": false, + "connection": { + "retries": 0, + "userAgentSuffix": "string", + "timeout": 60, + "enableCircularRedirects": false, + "enableCookies": false, + "useTrustStore": false + } + }, + "baseType": "raw", + "repoType": "proxy" + } \ No newline at end of file diff --git a/templates/shared_services/sonatype-nexus-vm/terraform/locals.tf b/templates/shared_services/sonatype-nexus-vm/terraform/locals.tf index c0484c712f..67cae90039 100644 --- a/templates/shared_services/sonatype-nexus-vm/terraform/locals.tf +++ b/templates/shared_services/sonatype-nexus-vm/terraform/locals.tf @@ -1,7 +1,7 @@ locals { core_vnet = "vnet-${var.tre_id}" core_resource_group_name = "rg-${var.tre_id}" - nexus_allowed_fqdns = "pypi.org,*.pypi.org,files.pythonhosted.org,security.ubuntu.com,archive.ubuntu.com,keyserver.ubuntu.com,repo.anaconda.com,*.docker.com,*.docker.io,conda.anaconda.org,azure.archive.ubuntu.com,packages.microsoft.com,repo.almalinux.org,download-ib01.fedoraproject.org,cran.r-project.org,cloud.r-project.org" + nexus_allowed_fqdns = "pypi.org,*.pypi.org,files.pythonhosted.org,security.ubuntu.com,archive.ubuntu.com,keyserver.ubuntu.com,repo.anaconda.com,*.docker.com,*.docker.io,conda.anaconda.org,azure.archive.ubuntu.com,packages.microsoft.com,repo.almalinux.org,download-ib01.fedoraproject.org,cran.r-project.org,cloud.r-project.org,download1.rstudio.org,*.snapcraftcontent.com,download.microsoft.com" nexus_allowed_fqdns_list = distinct(compact(split(",", replace(local.nexus_allowed_fqdns, " ", "")))) workspace_vm_allowed_fqdns = "r3.o.lencr.org,x1.c.lencr.org" workspace_vm_allowed_fqdns_list = distinct(compact(split(",", replace(local.workspace_vm_allowed_fqdns, " ", "")))) diff --git a/templates/shared_services/sonatype-nexus-vm/terraform/vm.tf b/templates/shared_services/sonatype-nexus-vm/terraform/vm.tf index 27a6a3d04f..79dfa04472 100644 --- a/templates/shared_services/sonatype-nexus-vm/terraform/vm.tf +++ b/templates/shared_services/sonatype-nexus-vm/terraform/vm.tf @@ -83,6 +83,7 @@ resource "azurerm_user_assigned_identity" "nexus_msi" { location = data.azurerm_resource_group.rg.location resource_group_name = local.core_resource_group_name tags = local.tre_shared_service_tags + lifecycle { ignore_changes = [tags] } } diff --git a/templates/workspace_services/guacamole/porter.yaml b/templates/workspace_services/guacamole/porter.yaml index f49e3b02e3..dd081ea0b9 100644 --- a/templates/workspace_services/guacamole/porter.yaml +++ b/templates/workspace_services/guacamole/porter.yaml @@ -1,7 +1,7 @@ --- schemaVersion: 1.0.0 name: tre-service-guacamole -version: 0.10.7 +version: 0.10.8 description: "An Azure TRE service for Guacamole" dockerfile: Dockerfile.tmpl registry: azuretre diff --git a/templates/workspace_services/guacamole/template_schema.json b/templates/workspace_services/guacamole/template_schema.json index c14d4e6d65..701667b2b8 100644 --- a/templates/workspace_services/guacamole/template_schema.json +++ b/templates/workspace_services/guacamole/template_schema.json @@ -41,7 +41,7 @@ "title": "Disable 'Paste'", "description": "Disable Paste functionality", "updateable": true, - "default": true + "default": false }, "guac_enable_drive": { "$id": "#/properties/guac_enable_drive", diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/porter.yaml b/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/porter.yaml index f36a668258..14ea690b02 100644 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/porter.yaml +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/porter.yaml @@ -1,7 +1,7 @@ --- schemaVersion: 1.0.0 name: tre-service-guacamole-linuxvm -version: 0.6.9 +version: 0.6.10 description: "An Azure TRE User Resource Template for Guacamole (Linux)" dockerfile: Dockerfile.tmpl registry: azuretre diff --git a/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/terraform/apt_sources_config.yml b/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/terraform/apt_sources_config.yml index 22b3418d5b..ae558a7085 100644 --- a/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/terraform/apt_sources_config.yml +++ b/templates/workspace_services/guacamole/user_resources/guacamole-azure-linuxvm/terraform/apt_sources_config.yml @@ -14,6 +14,8 @@ apt: deb [trusted=yes] $PRIMARY $RELEASE main restricted universe multiverse deb [trusted=yes] $PRIMARY $RELEASE-updates main restricted universe multiverse deb [trusted=yes] $SECURITY $RELEASE main restricted universe multiverse - deb [signed-by=/etc/apt/trusted.gpg.d/microsoft.gpg] ${nexus_proxy_url}/repository/microsoft-apt/ubuntu/18.04/prod $RELEASE main + deb [signed-by=/etc/apt/trusted.gpg.d/microsoft.gpg] ${nexus_proxy_url}/repository/microsoft-apt/ubuntu/${apt_sku}/prod $RELEASE main deb [signed-by=/etc/apt/trusted.gpg.d/microsoft.gpg] ${nexus_proxy_url}/repository/microsoft-apt/repos/edge stable main + deb [signed-by=/etc/apt/trusted.gpg.d/microsoft.gpg] ${nexus_proxy_url}/repository/microsoft-apt/repos/vscode stable main + deb [signed-by=/etc/apt/trusted.gpg.d/microsoft.gpg] ${nexus_proxy_url}/repository/microsoft-apt/repos/azure-cli stable main deb [signed-by=/etc/apt/trusted.gpg.d/docker-archive-keyring.gpg] ${nexus_proxy_url}/repository/docker/ $RELEASE stable diff --git a/ui/app/package.json b/ui/app/package.json index 72e418d50f..76c04f2b7a 100644 --- a/ui/app/package.json +++ b/ui/app/package.json @@ -1,6 +1,6 @@ { "name": "tre-ui", - "version": "0.5.10", + "version": "0.5.29", "private": true, "dependencies": { "@azure/msal-browser": "^2.35.0", diff --git a/ui/app/src/App.scss b/ui/app/src/App.scss index a1965cb715..316d62644c 100644 --- a/ui/app/src/App.scss +++ b/ui/app/src/App.scss @@ -26,7 +26,7 @@ code { .tre-top-nav { // box-shadow: 0 1px 2px 0px #033d68; - box-shadow: 0 1px 2px 0px #003E74; + box-shadow: 0 1px 2px 0px #0000CD; z-index: 100; } diff --git a/ui/app/src/assets/imperial_college_logo.svg b/ui/app/src/assets/imperial_college_logo.svg index a3e0fb051e..e39aca2d37 100644 --- a/ui/app/src/assets/imperial_college_logo.svg +++ b/ui/app/src/assets/imperial_college_logo.svg @@ -1,35 +1,30 @@ - - - - Logo - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/ui/app/src/assets/imperial_college_logo_old.svg b/ui/app/src/assets/imperial_college_logo_old.svg new file mode 100644 index 0000000000..a3e0fb051e --- /dev/null +++ b/ui/app/src/assets/imperial_college_logo_old.svg @@ -0,0 +1,35 @@ + + + + Logo + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui/app/src/components/shared/ComplexItemDisplay.tsx b/ui/app/src/components/shared/ComplexItemDisplay.tsx index a2b684b092..7759604b8e 100644 --- a/ui/app/src/components/shared/ComplexItemDisplay.tsx +++ b/ui/app/src/components/shared/ComplexItemDisplay.tsx @@ -91,8 +91,8 @@ const contentStyles = mergeStyleSets({ { flex: '1 1 auto', // borderTop: `4px solid ${theme.palette.themePrimary}`, - borderTop: `4px solid #006EAF`, - color: theme.palette.neutralPrimary, + borderTop: `4px solid #0000CD`, + color: '#0000CD', display: 'flex', alignItems: 'center', fontWeight: FontWeights.semibold, @@ -112,12 +112,12 @@ const contentStyles = mergeStyleSets({ }); const iconButtonStyles: Partial = { root: { - color: theme.palette.neutralPrimary, + color: '#0000CD', marginLeft: 'auto', marginTop: '4px', marginRight: '2px', }, rootHovered: { - color: theme.palette.neutralDark, + color: '#161A1D', }, }; diff --git a/ui/app/src/components/shared/Footer.tsx b/ui/app/src/components/shared/Footer.tsx index 3b2e9bce84..84988bafc7 100644 --- a/ui/app/src/components/shared/Footer.tsx +++ b/ui/app/src/components/shared/Footer.tsx @@ -127,7 +127,7 @@ const contentClass = mergeStyles([ { alignItems: 'center', // backgroundColor: theme.palette.themeDark, - backgroundColor: '#003E74', + backgroundColor: '#0000CD', color: theme.palette.white, lineHeight: '25px', padding: '0 20px', @@ -140,7 +140,7 @@ const iconButtonStyles: Partial = { color: theme.palette.white, }, rootHovered: { - color: theme.palette.neutralDark, + color: '#161A1D', }, }; diff --git a/ui/app/src/components/shared/ResourceContextMenu.tsx b/ui/app/src/components/shared/ResourceContextMenu.tsx index f57487b43a..af1dc36bd3 100644 --- a/ui/app/src/components/shared/ResourceContextMenu.tsx +++ b/ui/app/src/components/shared/ResourceContextMenu.tsx @@ -27,6 +27,15 @@ interface ResourceContextMenuProps { commandBar?: boolean } +enum ActionType { + Update = 'update', + Disable = 'disable', + Delete = 'delete', + Upgrade = 'upgrade', + Connect = 'connect', + Actions = 'actions' +} + export const ResourceContextMenu: React.FunctionComponent = (props: ResourceContextMenuProps) => { const apiCall = useAuthApiCall(); const workspaceCtx = useContext(WorkspaceContext); @@ -60,7 +69,7 @@ export const ResourceContextMenu: React.FunctionComponent { + let wsAuth = false; + let r = [] as Array; + + switch (type) { + case ResourceType.SharedService: + r = [RoleName.TREAdmin]; + break; + case ResourceType.WorkspaceService: + r = [WorkspaceRoleName.WorkspaceOwner] + wsAuth = true; + break; + case ResourceType.UserResource: + r = [WorkspaceRoleName.WorkspaceOwner, WorkspaceRoleName.AirlockManager, WorkspaceRoleName.WorkspaceDataEngineer]; + wsAuth = true; + break; + case ResourceType.Workspace: + r = [RoleName.TREAdmin, RoleName.ImperialTREAdmin]; + break; + default: + throw Error('Unsupported resource type.'); + } + // let currentUserRoles = appRoles.roles + // if (workspaceCtx.roles.length > 0){ + // currentUserRoles = currentUserRoles.concat(workspaceCtx.roles); + // } + const currentUserRoles = wsAuth ? workspaceCtx.roles : appRoles.roles; + console.log(action + " role " + type + " : " + r + " : " + currentUserRoles) + return r.some(role => currentUserRoles.includes(role)); + } + + // context menu let menuItems: Array = []; - menuItems = [ - { + if (requiredRoles(props.resource.resourceType, ActionType.Update)) { + menuItems.push({ key: 'update', text: 'Update', iconProps: { iconName: 'WindowEdit' }, @@ -116,23 +157,60 @@ export const ResourceContextMenu: React.FunctionComponent setShowDisable(true), disabled: (props.componentAction === ComponentAction.Lock) - }, - { + }) + } + + if (requiredRoles(props.resource.resourceType, ActionType.Delete)) { + menuItems.push({ key: 'delete', text: 'Delete', title: props.resource.isEnabled ? 'Resource must be disabled before deleting' : 'Delete this resource', iconProps: { iconName: 'Delete' }, onClick: () => setShowDelete(true), disabled: (props.resource.isEnabled || props.componentAction === ComponentAction.Lock) - }, - ]; + }) + } + + // menuItems = [ + // { + // key: 'update', + // text: 'Update', + // iconProps: { iconName: 'WindowEdit' }, + // onClick: () => createFormCtx.openCreateForm({ + // resourceType: props.resource.resourceType, + // updateResource: props.resource, + // resourceParent: parentResource, + // workspaceApplicationIdURI: workspaceCtx.workspaceApplicationIdURI, + // }), + // disabled: (props.componentAction === ComponentAction.Lock || requiredRoles(props.resource.resourceType, ActionType.Update)) + // }, + // { + // key: 'disable', + // text: props.resource.isEnabled ? 'Disable' : 'Enable', + // iconProps: { iconName: props.resource.isEnabled ? 'CirclePause' : 'PlayResume' }, + // onClick: () => setShowDisable(true), + // disabled: (props.componentAction === ComponentAction.Lock || requiredRoles(props.resource.resourceType, ActionType.Disable)) + // // disabled: (props.componentAction === ComponentAction.Lock) + // }, + // { + // key: 'delete', + // text: 'Delete', + // title: props.resource.isEnabled ? 'Resource must be disabled before deleting' : 'Delete this resource', + // iconProps: { iconName: 'Delete' }, + // onClick: () => setShowDelete(true), + // disabled: (props.resource.isEnabled || props.componentAction === ComponentAction.Lock || requiredRoles(props.resource.resourceType, ActionType.Delete)) + // }, + // ]; const shouldDisableConnect = () => { return props.componentAction === ComponentAction.Lock @@ -142,7 +220,7 @@ export const ResourceContextMenu: React.FunctionComponent { return props.componentAction === ComponentAction.Lock || actionsDisabledStates.includes(props.resource.deploymentStatus) @@ -190,7 +267,7 @@ export const ResourceContextMenu: React.FunctionComponent !upgrade.forceUpdateRequired) - if (nonMajorUpgrades?.length > 0) { + if (nonMajorUpgrades?.length > 0 && requiredRoles(props.resource.resourceType, ActionType.Upgrade)) { menuItems.push({ key: 'upgrade', text: 'Upgrade', @@ -219,12 +296,12 @@ export const ResourceContextMenu: React.FunctionComponent - : - + + : + } /> { showDisable && @@ -235,7 +312,7 @@ export const ResourceContextMenu: React.FunctionComponent setShowDelete(false)} resource={props.resource} /> } { - showCopyUrl && + showCopyUrl && setShowCopyUrl(false)} resource={props.resource} /> } { diff --git a/ui/app/src/components/shared/TopNav.tsx b/ui/app/src/components/shared/TopNav.tsx index eabecd77e5..0b8f365f8b 100644 --- a/ui/app/src/components/shared/TopNav.tsx +++ b/ui/app/src/components/shared/TopNav.tsx @@ -32,7 +32,7 @@ const theme = getTheme(); const contentClass = mergeStyles([ { // backgroundColor: theme.palette.themeDark, - backgroundColor: '#003E74', + backgroundColor: '#0000CD', color: theme.palette.white, lineHeight: '50px', padding: '0 10px 0 10px' diff --git a/ui/app/src/components/shared/airlock/AirlockReviewRequest.tsx b/ui/app/src/components/shared/airlock/AirlockReviewRequest.tsx index 4c8b771f55..30b114d9a7 100644 --- a/ui/app/src/components/shared/airlock/AirlockReviewRequest.tsx +++ b/ui/app/src/components/shared/airlock/AirlockReviewRequest.tsx @@ -337,8 +337,8 @@ const contentStyles = mergeStyleSets({ { flex: '1 1 auto', // borderTop: `4px solid ${theme.palette.themePrimary}`, - borderTop: `4px solid #006EAF`, - color: theme.palette.neutralPrimary, + borderTop: `4px solid #0000CD`, + color: '#0000CD', display: 'flex', alignItems: 'center', fontWeight: FontWeights.semibold, @@ -360,13 +360,13 @@ const contentStyles = mergeStyleSets({ const iconButtonStyles: Partial = { root: { - color: theme.palette.neutralPrimary, + color: '#0000CD', marginLeft: 'auto', marginTop: '4px', marginRight: '2px', }, rootHovered: { - color: theme.palette.neutralDark, + color: '#161A1D', }, }; diff --git a/ui/app/src/components/workspaces/WorkspaceHeader.tsx b/ui/app/src/components/workspaces/WorkspaceHeader.tsx index ffd998de41..16cd0b58ea 100644 --- a/ui/app/src/components/workspaces/WorkspaceHeader.tsx +++ b/ui/app/src/components/workspaces/WorkspaceHeader.tsx @@ -23,7 +23,7 @@ const theme = getTheme(); const contentClass = mergeStyles([ { // backgroundColor: theme.palette.themeDarker, - backgroundColor: '#002147', + backgroundColor: '#161A1D', color: theme.palette.white, lineHeight: '15px', padding: '0 20px',