Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ public/_redirects
package.json
package-lock.json
bun.lockb
*.xcf
*.xcf
*.glb
28 changes: 14 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,30 @@ Welcome to the kthcloud/console repository! This project is the web console for

## Table of Contents

- [☁️ kthcloud/console](#️-kthcloudconsole)
- [Table of Contents](#table-of-contents)
- [External dependencies](#external-dependencies)
- [Setup](#setup)
- [Contributing](#contributing)
- [Locales and translations](#locales-and-translations)
- [Other languages](#other-languages)
- [Formatting](#formatting)
- [License](#license)
- [☁️ kthcloud/console](#️-kthcloudconsole)
- [Table of Contents](#table-of-contents)
- [External dependencies](#external-dependencies)
- [Setup](#setup)
- [Contributing](#contributing)
- [Locales and translations](#locales-and-translations)
- [Other languages](#other-languages)
- [Formatting](#formatting)
- [License](#license)

## External dependencies

console uses these services for its functionality.

kthcloud maintained:

- [go-deploy](https://github.com/kthcloud/go-deploy): Backend for creation, and management of resources
- [alert](https://alert.app.cloud.cbh.kth.se/): Provides alerts
- [kthcloud iam](https://iam.cloud.cbh.kth.se): Provides user auth
- [go-deploy](https://github.com/kthcloud/go-deploy): Backend for creation, and management of resources
- [alert](https://alert.app.cloud.cbh.kth.se/): Provides alerts
- [kthcloud iam](https://iam.cloud.cbh.kth.se): Provides user auth

External:

- [Gravatar](https://gravatar.com): Provides user avatars
- [Google Fonts](https://fonts.google.com): Provides fonts
- [Gravatar](https://gravatar.com): Provides user avatars
- [Google Fonts](https://fonts.google.com): Provides fonts

## Setup

Expand Down
Binary file modified bun.lockb
Binary file not shown.
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"@types/punycode": "^2.1.4",
"@types/three": "^0.164.1",
"apexcharts": "^3.49.1",
"bun": "^1.2.22",
"change-case": "^5.4.4",
"crypto-js": "^4.2.0",
"http-status-codes": "^2.3.0",
Expand Down Expand Up @@ -65,13 +66,13 @@
"@typescript-eslint/parser": "^7.13.0",
"@vitejs/plugin-react": "^4.3.1",
"@vitejs/plugin-react-swc": "^3.7.0",
"eslint": "^9.4.0",
"eslint": "^9.35.0",
"eslint-plugin-react": "^7.34.2",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.7",
"prettier": "^3.3.2",
"prettier": "^3.6.2",
"prettier-plugin-nginx": "^1.0.3",
"typescript": "^5.4.5",
"vite": "^5.3.0"
"vite": "^7.1.6"
}
}
}
Binary file added public/static/models/Brain.glb
Binary file not shown.
2 changes: 1 addition & 1 deletion src/api/deploy/systemCapacities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@ export const getSystemCapacities = async (
throw res;
}
const cap = await res.json();
return cap.length > 0 ? cap[0]?.capacities ?? undefined : undefined;
return cap.length > 0 ? (cap[0]?.capacities ?? undefined) : undefined;
};
6 changes: 5 additions & 1 deletion src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,10 @@
"unschedulable": "Unschedulable",
"schedulable": "Schedulable",
"schedulable-description": "The last time this node reported its status, it was schedulable in the Kubernetes cluster",
"unschedulable-description": "The last time this node reported its status, it was unschedulable in the Kubernetes cluster"
"unschedulable-description": "The last time this node reported its status, it was unschedulable in the Kubernetes cluster",
"maia-intro-body": "Are you a researcher or student in biomedical engineering at KTH who needs state-of-the-art deep learning resources and compute? MAIA is the platform for developing, testing, and deploying medical AI, from early prototypes to real clinical workflows. You get straightforward access to compute via JupyterHub, SSH, or a virtual desktop. Our infrastructure, provided jointly with KTH Cloud, scales to different computational needs. Designed for collaboration and integration with hospital systems, MAIA lets you validate ideas and pilot your solutions in real-world clinical settings.",
"maia-intro-footer": "Request a MAIA account today and register a project to start building your medical AI solution.",
"maia-intro-header": "Discover ",
"button-get-started-maia": "Get started with MAIA"
}
}
8 changes: 7 additions & 1 deletion src/locales/se.json
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,8 @@
"joinedAt": "Gick med",
"addedAt": "Lades till",
"jobRestarted": "Jobb startat om",
"maia": "MAIA",
"maia-text": "BLA BLA BLA",
"funding-provided-by": "kthcloud samarbetar med och stöttas av många organisationer. Finansiellt stöd har vi fått från ",
"the": " ",
"program-and-the": "programmet och ",
Expand Down Expand Up @@ -554,6 +556,10 @@
"unschedulable": "Oschemaläggningsbar",
"schedulable": "Schemaläggningsbar",
"schedulable-description": "Senast denna nod rapporterade sin status var den schemaläggningsbar i Kubernetes-klustret",
"unschedulable-description": "Senast denna nod rapporterade sin status var den oschemaläggningsbar i Kubernetes-klustret"
"unschedulable-description": "Senast denna nod rapporterade sin status var den oschemaläggningsbar i Kubernetes-klustret",
"maia-intro-body": "Är du forskare eller student inom medicinsk teknik på KTH och behöver tillgång till datorresurser för deep learning och simuleringar? MAIA är en plattform för att utveckla, testa och implementera medicinsk AI, från tidiga prototyper till kliniska arbetsflöden. Du får åtkomst till beräkningsresurser via JupyterHub, SSH eller en virtual desktop. Vår infrastruktur, som tillhandahålls tillsammans med KTH Cloud, kan möta olika typer av behov för beräkningsresurser. MAIA är utformad för samarbete och integration med sjukhussystem och gör det möjligt för dig att validera idéer och testa dina lösningar även i kliniska miljöer.",
"maia-intro-footer": "Skapa ett MAIA-konto redan idag och registrera ett projekt",
"maia-intro-header": "Testa ",
"button-get-started-maia": "Kom igång med MAIA"
}
}
2 changes: 2 additions & 0 deletions src/pages/landing/Landing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Box, Container } from "@mui/material";
import { useKeycloak } from "@react-keycloak/web";
import LoadingPage from "../../components/LoadingPage";
import Funding from "./components/funding/Funding";
import Maia from "./components/maia/Maia";
import { AlertList } from "../../components/AlertList";
import { useContext, useEffect } from "react";
import { AuthContextWrapper } from "../../contexts/AuthContextWrapper";
Expand Down Expand Up @@ -40,6 +41,7 @@ export function Landing() {

<Hero />
<Intro />
<Maia />
<Funding />
</Page>
);
Expand Down
43 changes: 43 additions & 0 deletions src/pages/landing/components/maia/Brain.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/* eslint-disable react/no-unknown-property */
import { Canvas } from "@react-three/fiber";
import { BrainMesh } from "./BrainMesh";
import * as THREE from "three";

export function Brain({
mobile,
position,
}: {
mobile: boolean;
position: number[];
}) {
return (
<Canvas
style={
mobile
? { height: "250px", width: "100%" }
: {
position: "absolute",
top: "0",
left: "0",
height: "100%",
width: "100%",
}
}
>
<ambientLight intensity={0.5} />
<directionalLight position={[5, 5, 5]} intensity={2} color={"#fffecc"} />
<directionalLight position={[5, -5, 5]} intensity={1} color={"#faa"} />
<directionalLight position={[-5, 5, 5]} intensity={1} color={"#aaf"} />

<BrainMesh
mobile={mobile}
position={position}
props={{
material: new THREE.MeshBasicMaterial({
color: "yellow",
}),
}}
/>
</Canvas>
);
}
54 changes: 54 additions & 0 deletions src/pages/landing/components/maia/BrainMesh.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// @ts-nocheck

import { useRef, useEffect, useState } from "react";
import { useFrame, useLoader } from "@react-three/fiber";
import { GLTFLoader } from "three-stdlib";
import { Vector3 } from "three";

const url = "/static/models/Brain.glb";

function BrainModel() {
const gltf = useLoader(GLTFLoader, url);
gltf.scene.traverse((child) => {
if (child.material) child.material.metalness = 0;
});

return <primitive object={gltf.scene}></primitive>;
}

export function BrainMesh({ mobile, position, props }) {
const [mouseCoordinates, setMouseCoordinates] = useState({ x: 0, y: 0 });
const meshRef = useRef();

const mouseMoveHandler = (event) => {
setMouseCoordinates({
x: event.clientX,
y: event.clientY,
});
};

useEffect(() => {
window.addEventListener("mousemove", mouseMoveHandler);
return () => {
window.removeEventListener("mousemove", mouseMoveHandler);
};
}, []);

useFrame(({ camera }, delta) => {
if (mobile) {
meshRef.current.rotation.y += delta;
} else {
let x = mouseCoordinates.x / window.innerWidth;
let y = 1 - mouseCoordinates.y / window.innerHeight;
const vector = new Vector3(x, y, 0);
vector.unproject(camera);
meshRef.current.rotation.set(1 - vector.y * 20, vector.x * 10, 0);
}
});

return (
<mesh position={position} scale={0.03} ref={meshRef} {...props}>
<BrainModel />
</mesh>
);
}
106 changes: 106 additions & 0 deletions src/pages/landing/components/maia/Maia.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import {
Grid,
Card,
Container,
Typography,
Button,
Stack,
Box,
} from "@mui/material";
import "./intro.css";
import { Brain } from "./Brain";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";

const Maia = () => {
const { t } = useTranslation();

return (
<Box component="div" sx={{ overflow: "hidden", marginTop: 5 }}>
<Container maxWidth="lg">
<Card sx={{ boxShadow: 20, background: "#242424ff", color: "#ffffff" }}>
<Grid
container
justifyContent="center"
alignItems="center"
rowSpacing={4}
sx={{
padding: {
xs: "50px 16px",
sm: "50px 58px",
md: "50px 50px 50px 50px",
},
}}
>
{/* Text block - comes first on all screen sizes */}
<Grid
item
sx={{ zIndex: 6 }}
xs={12}
md={5}
order={{ xs: 1, md: 1 }}
>
<Typography
variant="h2"
sx={{
fontWeight: "400",
marginBottom: "40px",
textAlign: { xs: "center", md: "left" },
}}
>
{t("maia-intro-header")} <strong>MAIA</strong>
</Typography>

<Typography variant="subtitle1" sx={{ marginBottom: "40px" }}>
{t("maia-intro-body")}
</Typography>
<Stack direction="row" spacing={2}>
<Typography variant="subtitle2" sx={{ marginBottom: "40px" }}>
{t("maia-intro-footer")}
</Typography>

<Button
variant="contained"
sx={{ whiteSpace: "nowrap", px: 8 }}
component={Link}
to={import.meta.env.VITE_MAIA_URL}
>
{t("button-get-started-maia")}
</Button>
</Stack>
</Grid>

{/* Brain block - comes second on desktop, second (below) on mobile */}
<Grid item xs={12} md={7} order={{ xs: 2, md: 2 }}>
{/* Desktop brain */}
<Box
component="div"
sx={{
display: { xs: "none", sm: "none", md: "block" },
textAlign: "right",
zIndex: 1,
}}
>
<Brain position={[3, 0, 0]} mobile={false} />
</Box>

{/* Mobile brain */}
<Box
component="div"
sx={{
display: { xs: "block", sm: "block", md: "none" },
padding: 0,
textAlign: "center",
}}
>
<Brain mobile position={[0, 0, 0]} />
</Box>
</Grid>
</Grid>
</Card>
</Container>
</Box>
);
};

export default Maia;
43 changes: 43 additions & 0 deletions src/pages/landing/components/maia/intro.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
.lil-curve {
width: 64px;
text-align: center;
}
.jeremy-card {
z-index: 10;
}
.blur {
position: relative;
top: 100px;
left: -150px;
z-index: 1;
margin-top: -100px;
width: 500px;
}

@media screen and (min-width: 900px) {
.lil-curve {
transform: translateY(-100px);
}
}
@media screen and (max-width: 900px) {
.blur {
position: relative;
top: 0px;
left: -10px;
margin: 0 auto;
margin-top: -100px;
margin-bottom: -30px;

text-align: center;
}
}

@media screen and (max-width: 600px) {
.blur {
top: 0px;
left: -500px;
margin-bottom: -30px;

text-align: center;
}
}