Compare commits
82 Commits
1d45fca783
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| c65b192978 | |||
| 06115bf93b | |||
| dfcea79f84 | |||
| 06fbf2e4de | |||
| b453dd3b9e | |||
| 46dc855a06 | |||
| 623a909e43 | |||
| 9a390bb831 | |||
| 0e3ae13474 | |||
| dba043a4af | |||
| 8a2536dd5d | |||
| 1d803dbe64 | |||
| 99eb2193cd | |||
| ea5e6f2f06 | |||
| fc3bf98fbd | |||
| 1c04f377bb | |||
| d0a576e76f | |||
| 345bc2c08a | |||
| 9d2c2b7bcd | |||
| 2a25da1e77 | |||
| 2209f36943 | |||
| 4c6bb29f9c | |||
| 173b2778ce | |||
| f28607e405 | |||
| dc3b4628ec | |||
| 12d7e880d9 | |||
| c427234a45 | |||
| b57b11c469 | |||
| e40252de99 | |||
| 8c39e38fb3 | |||
| a298941e0c | |||
| a516f020ef | |||
| 7f27e0c673 | |||
| 4879071e08 | |||
| 45410eaf4f | |||
| 280511118c | |||
| ae8a3643a4 | |||
| 5c9d8de03d | |||
| f980da7daf | |||
| e7c9e6d4e0 | |||
| 0cdb868e6e | |||
| 7d29c9abbc | |||
| c8ca07904b | |||
| 649f30075d | |||
| 0052dbe30f | |||
| 897b04fa4f | |||
| 4f2f98988f | |||
| 9dd04356fc | |||
| 9654ea3050 | |||
| 82c8b1d451 | |||
| b5877f36f5 | |||
| cbca63c43f | |||
| 9f95564022 | |||
| c451ec5b92 | |||
| 4c0ad887b5 | |||
| c7746ff91d | |||
| aad5dc83e1 | |||
| c9898eb26c | |||
| 2a5702db4b | |||
| 2badabb167 | |||
| 3f5facc265 | |||
| 9d15e4e50e | |||
| b45468fcbd | |||
| e5534d4f1d | |||
| a3580be305 | |||
| 47660ff80c | |||
| 940435cc8a | |||
| 6d76e17613 | |||
| 1d37e5a39f | |||
| 9cdb13c753 | |||
| 97d9372642 | |||
| 44d312ab4e | |||
| 8fcf4e8758 | |||
| 8ff3487198 | |||
| 9cd3573a00 | |||
| a8824e3864 | |||
| 92913fb20f | |||
| 672d3df1af | |||
| 6aa16bd0ef | |||
| a50caacfce | |||
| 71b095a0e4 | |||
| c7e09cb8c4 |
73
.gitea_deprecated/workflows/build.yaml
Normal file
73
.gitea_deprecated/workflows/build.yaml
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
name: Build the portfolio website
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-portfolio-website:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: "20"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: cd frontend && npm install
|
||||||
|
|
||||||
|
- name: Build the project
|
||||||
|
run: cd frontend && npm run build
|
||||||
|
|
||||||
|
build-and-release-image:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container: catthehacker/ubuntu:act-latest
|
||||||
|
needs: build-portfolio-website
|
||||||
|
if: success()
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # all history for all branches and tags
|
||||||
|
|
||||||
|
- name: Create kubeconfig
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.kube
|
||||||
|
echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > ~/.kube/config
|
||||||
|
chmod 600 ~/.kube/config
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
with:
|
||||||
|
driver: kubernetes
|
||||||
|
driver-opts: |
|
||||||
|
namespace=gitea
|
||||||
|
qemu.install=true
|
||||||
|
|
||||||
|
- name: Login to Docker registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ secrets.DOCKER_REGISTRY }}
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Generate timestamp
|
||||||
|
id: timestamp
|
||||||
|
run: echo "timestamp=$(date +%Y%m%d%H%M%S)" >> $GITEA_OUTPUT
|
||||||
|
|
||||||
|
- name: Build Docker image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: Dockerfile
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
${{ secrets.DOCKER_REGISTRY }}/taqi/portfolio/my-portfolio-app:latest
|
||||||
|
${{ secrets.DOCKER_REGISTRY }}/taqi/portfolio/my-portfolio-app:1.0.0-${{ steps.timestamp.outputs.timestamp }}
|
||||||
9
.trivyignore
Normal file
9
.trivyignore
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
CVE-2024-4068
|
||||||
|
CVE-2024-21538
|
||||||
|
CVE-2024-21536
|
||||||
|
CVE-2025-7783
|
||||||
|
CVE-2024-29415
|
||||||
|
CVE-2022-23539
|
||||||
|
CVE-2022-24771
|
||||||
|
CVE-2022-24772
|
||||||
|
CVE-2024-29180
|
||||||
35
.woodpecker/build.yaml
Normal file
35
.woodpecker/build.yaml
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
when:
|
||||||
|
- event: [push, pull_request, manual]
|
||||||
|
branch: [master, feature/*]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: trivy-scan
|
||||||
|
image: aquasec/trivy:latest
|
||||||
|
commands:
|
||||||
|
- trivy fs --scanners vuln,config --exit-code 1 --ignorefile .trivyignore --severity HIGH,CRITICAL frontend/
|
||||||
|
|
||||||
|
- name: lint-frontend
|
||||||
|
image: node:24
|
||||||
|
commands:
|
||||||
|
- cd frontend
|
||||||
|
- npm install
|
||||||
|
- npm run lint
|
||||||
|
|
||||||
|
- name: build-and-publish
|
||||||
|
image: woodpeckerci/plugin-docker-buildx
|
||||||
|
settings:
|
||||||
|
registry:
|
||||||
|
from_secret: docker_registry
|
||||||
|
repo:
|
||||||
|
from_secret: portfolio_docker_repo
|
||||||
|
tags:
|
||||||
|
- latest
|
||||||
|
- 1.0.0-${CI_PIPELINE_NUMBER} # Ref: https://woodpecker-ci.org/docs/usage/environment
|
||||||
|
skip_tls_verify: false # set to true for testing registries ONLY with self-signed certs
|
||||||
|
build_args:
|
||||||
|
- COMMIT_SHA=${CI_COMMIT_SHA}
|
||||||
|
- COMMIT_AUTHOR_EMAIL=${CI_COMMIT_AUTHOR_EMAIL}
|
||||||
|
username:
|
||||||
|
from_secret: docker-username
|
||||||
|
password:
|
||||||
|
from_secret: docker-password
|
||||||
@ -1,5 +1,5 @@
|
|||||||
# Step 1: Build the React app
|
# Step 1: Build the React app
|
||||||
FROM node:20 AS build
|
FROM node:24-slim AS build
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|||||||
24
README.md
24
README.md
@ -1,5 +1,4 @@
|
|||||||
My Portfolio Website
|
# My Portfolio Website
|
||||||
=====================
|
|
||||||
|
|
||||||
# Overview
|
# Overview
|
||||||
|
|
||||||
@ -25,10 +24,25 @@ This website is packaged as a container and deployed using nginx.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
source .env
|
source .env
|
||||||
docker build -t my-portfolio-app .
|
docker build -t portfolio .
|
||||||
docker tag my-portfolio-app:latest $DOCKER_REGISTRY/my-portfolio-app:latest
|
docker tag portfolio:latest $DOCKER_REGISTRY/portfolio:latest
|
||||||
docker push $DOCKER_REGISTRY/my-portfolio-app:latest
|
docker push $DOCKER_REGISTRY/portfolio:latest
|
||||||
|
|
||||||
# Check the registry
|
# Check the registry
|
||||||
curl -u user:pass https://$DOCKER_REGISTRY/v2/_catalog
|
curl -u user:pass https://$DOCKER_REGISTRY/v2/_catalog
|
||||||
|
|
||||||
|
# Or if using gitea registry
|
||||||
|
curl --netrc -X GET https://gitea.yourdomain.com/v2/_catalog
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# CI/CD
|
||||||
|
|
||||||
|
Run in Gitea Actions within kubernetes cluster
|
||||||
|
|
||||||
|
Current, the project has workflow files for:
|
||||||
|
|
||||||
|
- Build and push the container to the registry
|
||||||
|
- Deploy the container to the Kubernetes cluster
|
||||||
|
|
||||||
|
Thus making a commit to the master branch will automatically build the
|
||||||
|
container and deploy it to the cluster.
|
||||||
|
|||||||
17379
frontend/package-lock.json
generated
17379
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -18,7 +18,8 @@
|
|||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-icons": "^5.3.0",
|
"react-icons": "^5.3.0",
|
||||||
"react-markdown": "^9.0.3",
|
"react-markdown": "^9.0.3",
|
||||||
"react-router-dom": "^7.1.1"
|
"react-router-dom": "^7.1.1",
|
||||||
|
"styled-component": "^2.8.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.9.0",
|
"@eslint/js": "^9.9.0",
|
||||||
@ -35,6 +36,9 @@
|
|||||||
"tailwindcss": "^3.4.11",
|
"tailwindcss": "^3.4.11",
|
||||||
"typescript": "^5.5.3",
|
"typescript": "^5.5.3",
|
||||||
"typescript-eslint": "^8.0.1",
|
"typescript-eslint": "^8.0.1",
|
||||||
"vite": "^5.4.1"
|
"vite": "^7.0.6"
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"@types/minimatch": "5.1.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,3 +4,8 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto 1fr auto;
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@ import { Routes, Route } from 'react-router-dom'
|
|||||||
import './App.css'
|
import './App.css'
|
||||||
import Home from './pages/Home'
|
import Home from './pages/Home'
|
||||||
import Navbar from './components/Navbar'
|
import Navbar from './components/Navbar'
|
||||||
|
import Sidebar from './components/Sidebar'
|
||||||
import Footer from './components/Footer'
|
import Footer from './components/Footer'
|
||||||
import Experience from './pages/Experience'
|
import Experience from './pages/Experience'
|
||||||
import Projects from './pages/Projects'
|
import Projects from './pages/Projects'
|
||||||
@ -24,6 +25,11 @@ function App() {
|
|||||||
<main className="bg-amber-50 px-10 dark:bg-gray-900">
|
<main className="bg-amber-50 px-10 dark:bg-gray-900">
|
||||||
<section className="min-h-screen">
|
<section className="min-h-screen">
|
||||||
<Navbar toggleDarkMode={toggleDarkMode} darkMode={darkMode} />
|
<Navbar toggleDarkMode={toggleDarkMode} darkMode={darkMode} />
|
||||||
|
<div className="md:flex md:flex-row md:h-full">
|
||||||
|
<div className="mb-4 md:w-1/4 md:max-w-[260px] md:max-h-[900px] border-2 border-gray-300 dark:border-gray-700 rounded-lg shadow-sm">
|
||||||
|
<Sidebar />
|
||||||
|
</div>
|
||||||
|
<div className="md:flex-1">
|
||||||
<Router>
|
<Router>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Home />} />
|
<Route path="/" element={<Home />} />
|
||||||
@ -32,6 +38,9 @@ function App() {
|
|||||||
<Route path="/Interests" element={<Interests />} />
|
<Route path="/Interests" element={<Interests />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</Router>
|
</Router>
|
||||||
|
</div>
|
||||||
|
<div className="hidden md:block md:w-1/4 md:max-w-[260px] md:max-h-[900px]"></div>
|
||||||
|
</div>
|
||||||
<Footer />
|
<Footer />
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@ -29,7 +29,7 @@ const Footer = () => {
|
|||||||
|
|
||||||
<div className="flex items-center gap-2 text-xs">
|
<div className="flex items-center gap-2 text-xs">
|
||||||
<Coffee size={14} className="inline-block" />
|
<Coffee size={14} className="inline-block" />
|
||||||
<span>Powered by coffee and countless hours of debugging</span>
|
<span>Powered by coffee and Love</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { COLORS, SOCIALLINKS } from '../constants'
|
import { COLORS } from '../constants'
|
||||||
import { Tooltip } from './Tooltip'
|
|
||||||
|
|
||||||
const Introduction = () => {
|
const Introduction = () => {
|
||||||
const BoldStyle = 'text-blue-900 dark:text-blue-300 font-semibold'
|
const BoldStyle = 'text-blue-900 dark:text-blue-300 font-semibold'
|
||||||
@ -35,24 +34,6 @@ const Introduction = () => {
|
|||||||
and infrastructure automation to implementing robust testing frameworks.
|
and infrastructure automation to implementing robust testing frameworks.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-center gap-8 py-3">
|
|
||||||
{SOCIALLINKS.map((link, index) => (
|
|
||||||
<div key={index} className="group relative">
|
|
||||||
<Tooltip label={link.label} position="top">
|
|
||||||
<a
|
|
||||||
href={link.href}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
className={`${COLORS.PRIMARY} ${COLORS.DARK_PRIMARY} hover:text-sky-600 dark:hover:text-sky-500 transition-colors duration-200`}
|
|
||||||
aria-label={link.label}
|
|
||||||
>
|
|
||||||
{link.icon}
|
|
||||||
</a>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,11 +35,13 @@ const Navbar: React.FC<NavProps> = ({ toggleDarkMode, darkMode }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isMenuOpen) {
|
||||||
document.addEventListener('mousedown', handleClickoutside)
|
document.addEventListener('mousedown', handleClickoutside)
|
||||||
|
}
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('mousedown', handleClickoutside)
|
document.removeEventListener('mousedown', handleClickoutside)
|
||||||
}
|
}
|
||||||
})
|
}, [isMenuOpen])
|
||||||
|
|
||||||
const menuItem = [
|
const menuItem = [
|
||||||
{ title: 'Home', href: '/' },
|
{ title: 'Home', href: '/' },
|
||||||
@ -102,7 +104,8 @@ const Navbar: React.FC<NavProps> = ({ toggleDarkMode, darkMode }) => {
|
|||||||
|
|
||||||
{/* Dropdown Menu */}
|
{/* Dropdown Menu */}
|
||||||
{isMenuOpen && (
|
{isMenuOpen && (
|
||||||
<div className="absolute right-0 mt-3 w-36 rounded-md shadow-lg bg-amber-50 dark:bg-gray-800 ring-1 ring-black ring-opacity-5">
|
// Dropdown Menu. The z value is set to 10 so that it appears above other elements.
|
||||||
|
<div className="absolute right-0 mt-3 w-36 rounded-md shadow-lg bg-amber-50 dark:bg-gray-800 ring-1 ring-black ring-opacity-5 z-10">
|
||||||
<div className="py-1" role="menu" aria-orientation="vertical">
|
<div className="py-1" role="menu" aria-orientation="vertical">
|
||||||
{menuItem.map((item) => (
|
{menuItem.map((item) => (
|
||||||
<a
|
<a
|
||||||
|
|||||||
75
frontend/src/components/Sidebar.tsx
Normal file
75
frontend/src/components/Sidebar.tsx
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import { COLORS, SOCIALLINKS } from '../constants'
|
||||||
|
import { Tooltip } from './Tooltip'
|
||||||
|
|
||||||
|
interface LinkProps {
|
||||||
|
href: string
|
||||||
|
children: React.ReactNode
|
||||||
|
icon: React.ReactNode
|
||||||
|
label: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Link: React.FC<LinkProps> = ({ icon, href, children, label }) => {
|
||||||
|
return (
|
||||||
|
<div className="transition-all duration-200 hover:translate-x-1">
|
||||||
|
<div className="m-2 border border-gray-200 dark:border-gray-700 p-2 md:p-3 rounded-lg hover:shadow-md">
|
||||||
|
<Tooltip label={label} position="top" additionalClass="md:hidden">
|
||||||
|
<a
|
||||||
|
href={href}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="flex items-center text-gray-700 dark:text-gray-200 hover:text-blue-600 dark:hover:text-blue-400"
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
<span className="text-sm font-medium hidden md:inline">{children}</span>
|
||||||
|
</a>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Sidebar = () => {
|
||||||
|
const SectionTitle = ({ children }: { children: React.ReactNode }) => (
|
||||||
|
<h2
|
||||||
|
className={`mt-4 mb-4 text-lg font-semibold ${COLORS.PRIMARY} ${COLORS.DARK_PRIMARY} hidden md:block`}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</h2>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`grid grid-cols-4 md:grid-cols-1 p-2 md:p-6 max-w-xs mx-auto ${COLORS.PRIMARY} ${COLORS.DARK_PRIMARY} rounded-xl shadow-sm`}
|
||||||
|
>
|
||||||
|
<SectionTitle>Contact</SectionTitle>
|
||||||
|
{SOCIALLINKS.contact.map((link, index) => (
|
||||||
|
<Link key={index} icon={link.icon} href={link.href} label={link.text}>
|
||||||
|
{link.text}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<SectionTitle>Connect</SectionTitle>
|
||||||
|
{SOCIALLINKS.connect.map((link, index) => (
|
||||||
|
<Link key={index} icon={link.icon} href={link.href} label={link.text}>
|
||||||
|
{link.text}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<SectionTitle>Follow</SectionTitle>
|
||||||
|
{SOCIALLINKS.follow.map((link, index) => (
|
||||||
|
<Link key={index} icon={link.icon} href={link.href} label={link.text}>
|
||||||
|
{link.text}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<SectionTitle>Achievements</SectionTitle>
|
||||||
|
{SOCIALLINKS.publications.map((link, index) => (
|
||||||
|
<Link key={index} icon={link.icon} href={link.href} label={link.text}>
|
||||||
|
{link.text}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Sidebar
|
||||||
@ -1,6 +1,33 @@
|
|||||||
import { COLORS, SKILLS } from '../constants'
|
import pythonIcon from '../assets/python.svg'
|
||||||
|
import robotIcon from '../assets/robotframework-svgrepo-com.svg'
|
||||||
|
import goIcon from '../assets/go-original.svg'
|
||||||
|
import reactIcon from '../assets/react.svg'
|
||||||
|
import ansibleIcon from '../assets/ansible.svg'
|
||||||
|
import terraformIcon from '../assets/terraform-icon.svg'
|
||||||
|
import jenkinsIcon from '../assets/jenkins.svg'
|
||||||
|
import gitIcon from '../assets/git-icon.svg'
|
||||||
|
import dockerIcon from '../assets/docker-icon.svg'
|
||||||
|
import kubernetesIcon from '../assets/kubernetes.svg'
|
||||||
|
import prometheusIcon from '../assets/prometheus.svg'
|
||||||
|
import grafanaIcon from '../assets/grafana.svg'
|
||||||
|
import { COLORS } from '../constants'
|
||||||
|
|
||||||
const Skills = () => {
|
const Skills = () => {
|
||||||
|
const skills = [
|
||||||
|
{ name: 'Python', icon: pythonIcon },
|
||||||
|
{ name: 'Golang', icon: goIcon },
|
||||||
|
{ name: 'React', icon: reactIcon },
|
||||||
|
{ name: 'Robot Framework', icon: robotIcon },
|
||||||
|
{ name: 'Ansible', icon: ansibleIcon },
|
||||||
|
{ name: 'Terraform', icon: terraformIcon },
|
||||||
|
{ name: 'Jenkins', icon: jenkinsIcon },
|
||||||
|
{ name: 'Git', icon: gitIcon },
|
||||||
|
{ name: 'Docker', icon: dockerIcon },
|
||||||
|
{ name: 'Kubernetes', icon: kubernetesIcon },
|
||||||
|
{ name: 'Prometheus', icon: prometheusIcon },
|
||||||
|
{ name: 'Grafana', icon: grafanaIcon },
|
||||||
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1 className={`text-2xl py-5 font-burtons ${COLORS.PRIMARY} ${COLORS.DARK_PRIMARY}`}>
|
<h1 className={`text-2xl py-5 font-burtons ${COLORS.PRIMARY} ${COLORS.DARK_PRIMARY}`}>
|
||||||
@ -9,7 +36,7 @@ const Skills = () => {
|
|||||||
<div
|
<div
|
||||||
className={`grid grid-cols-4 gap-6 text-md py-5 leading-8 ${COLORS.TEXT} ${COLORS.DARK_TEXT} mx-auto max-w-2xl md:text-xl`}
|
className={`grid grid-cols-4 gap-6 text-md py-5 leading-8 ${COLORS.TEXT} ${COLORS.DARK_TEXT} mx-auto max-w-2xl md:text-xl`}
|
||||||
>
|
>
|
||||||
{SKILLS.map((skill) => (
|
{skills.map((skill) => (
|
||||||
<div key={skill.name} className="flex flex-col items-center">
|
<div key={skill.name} className="flex flex-col items-center">
|
||||||
<img src={skill.icon} alt={skill.name} className="h-10 w-10" />
|
<img src={skill.icon} alt={skill.name} className="h-10 w-10" />
|
||||||
<p className="mt-2 text-center">{skill.name}</p>
|
<p className="mt-2 text-center">{skill.name}</p>
|
||||||
|
|||||||
@ -2,9 +2,10 @@ export interface TooltipProps {
|
|||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
label: string
|
label: string
|
||||||
position: 'top' | 'bottom' | 'left' | 'right'
|
position: 'top' | 'bottom' | 'left' | 'right'
|
||||||
|
additionalClass?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Tooltip: React.FC<TooltipProps> = ({ children, label, position }) => {
|
export const Tooltip: React.FC<TooltipProps> = ({ children, label, position, additionalClass }) => {
|
||||||
const tooltipStyles = {
|
const tooltipStyles = {
|
||||||
top: 'bottom-full left-1/2 -translate-x-1/2',
|
top: 'bottom-full left-1/2 -translate-x-1/2',
|
||||||
bottom: 'top-full left-1/2 -translate-x-1/2',
|
bottom: 'top-full left-1/2 -translate-x-1/2',
|
||||||
@ -12,7 +13,7 @@ export const Tooltip: React.FC<TooltipProps> = ({ children, label, position }) =
|
|||||||
right: 'left-full top-1/2 -translate-y-1/2',
|
right: 'left-full top-1/2 -translate-y-1/2',
|
||||||
}
|
}
|
||||||
const tooltipPosition: string = tooltipStyles[position]
|
const tooltipPosition: string = tooltipStyles[position]
|
||||||
const tooltipClass = `pointer-events-none absolute mt-2 whitespace-nowrap rounded bg-slate-800 px-2 py-1 text-xs text-slate-100 opacity-0 transition before:absolute before:left-1/2 before:top-full before:-translate-x-1/2 before:border-4 before:border-transparent before:border-t-slate-800 before:content-[''] group-hover:opacity-100 ${tooltipPosition}`
|
const tooltipClass = `pointer-events-none absolute mt-2 whitespace-nowrap rounded bg-slate-800 px-2 py-1 text-xs text-slate-100 opacity-0 transition before:absolute before:left-1/2 before:top-full before:-translate-x-1/2 before:border-4 before:border-transparent before:border-t-slate-800 before:content-[''] group-hover:opacity-100 ${tooltipPosition} ${additionalClass}`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="group relative">
|
<div className="group relative">
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
import { Linkedin, Github, Award, Link } from 'lucide-react'
|
import { Linkedin, Github, Award, Link } from 'lucide-react'
|
||||||
|
import { Aperture, Instagram, Mail, Globe, ScrollText } from 'lucide-react'
|
||||||
|
import { Camera, Plane, Film, Server, Cpu, Trophy, Car, Gamepad2 } from 'lucide-react'
|
||||||
import pythonIcon from './assets/python.svg'
|
import pythonIcon from './assets/python.svg'
|
||||||
import robotIcon from './assets/robotframework-svgrepo-com.svg'
|
import robotIcon from './assets/robotframework-svgrepo-com.svg'
|
||||||
import goIcon from './assets/go-original.svg'
|
import goIcon from './assets/go-original.svg'
|
||||||
@ -26,7 +28,6 @@ export const EMAIL = 'taqitahmid@gmail.com'
|
|||||||
export const RESUME =
|
export const RESUME =
|
||||||
'https://www.linkedin.com/in/taqi-tahmid/overlay/1735981754176/single-media-viewer/?profileId=ACoAACDU_GsBCgKtvw2bmzbVwTy2WixBG6-e3JM'
|
'https://www.linkedin.com/in/taqi-tahmid/overlay/1735981754176/single-media-viewer/?profileId=ACoAACDU_GsBCgKtvw2bmzbVwTy2WixBG6-e3JM'
|
||||||
|
|
||||||
|
|
||||||
export const PROJECTS = [
|
export const PROJECTS = [
|
||||||
{
|
{
|
||||||
title: 'Self-Hosted Kubernetes Homelab Cluster',
|
title: 'Self-Hosted Kubernetes Homelab Cluster',
|
||||||
@ -87,23 +88,58 @@ export const PROJECTS = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
export const SOCIALLINKS = [
|
export const iconClass = 'text-blue-600 dark:text-blue-400 mr-3'
|
||||||
|
|
||||||
|
export const SOCIALLINKS = {
|
||||||
|
contact: [
|
||||||
{
|
{
|
||||||
icon: <Linkedin size={32} />,
|
icon: <Mail size={20} className={iconClass} />,
|
||||||
|
href: 'mailto:taqitahmid@gmail.com',
|
||||||
|
text: 'Email Me',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <Globe size={20} className={iconClass} />,
|
||||||
|
href: 'https://portfolio.tahmidcloud.com/',
|
||||||
|
text: 'Website',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
connect: [
|
||||||
|
{
|
||||||
|
icon: <Linkedin size={20} className={iconClass} />,
|
||||||
href: 'https://www.linkedin.com/in/taqi-tahmid/',
|
href: 'https://www.linkedin.com/in/taqi-tahmid/',
|
||||||
label: 'LinkedIn',
|
text: 'LinkedIn',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <Github size={32} />,
|
icon: <Github size={20} className={iconClass} />,
|
||||||
href: 'https://github.com/TheTaqiTahmid',
|
href: 'https://github.com/theTaqiTahmid',
|
||||||
label: 'GitHub',
|
text: 'GitHub',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
follow: [
|
||||||
|
{
|
||||||
|
icon: <Aperture size={20} className={iconClass} />,
|
||||||
|
href: 'https://500px.com/p/taqi1203050?view=photos',
|
||||||
|
text: 'Photography',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <Award size={32} />,
|
icon: <Instagram size={20} className={iconClass} />,
|
||||||
href: 'https://ti-user-certificates.s3.amazonaws.com/e0df7fbf-a057-42af-8a1f-590912be5460/3da54db2-f994-4148-a0ca-705ae1d748cd-mohammad-taqi-tahmid-094cf8b4-0db8-4a9f-b787-b4efbb2a90fe-certificate.pdf',
|
href: 'https://www.instagram.com/tahmidtaqi/',
|
||||||
label: 'CKA Certificate',
|
text: 'Instagram',
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
|
publications: [
|
||||||
|
{
|
||||||
|
icon: <Award size={20} className={iconClass} />,
|
||||||
|
href: 'https://www.credly.com/badges/abb049aa-d811-4954-a460-8c7351ceba3e/public_url',
|
||||||
|
text: 'CKA Certification',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <ScrollText size={20} className={iconClass} />,
|
||||||
|
href: 'https://scholar.google.fi/citations?user=w3BoP0AAAAAJ&hl=en',
|
||||||
|
text: 'Google Scholar',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
export const SKILLS = [
|
export const SKILLS = [
|
||||||
{ name: 'Python', icon: pythonIcon },
|
{ name: 'Python', icon: pythonIcon },
|
||||||
@ -119,3 +155,128 @@ export const SKILLS = [
|
|||||||
{ name: 'Prometheus', icon: prometheusIcon },
|
{ name: 'Prometheus', icon: prometheusIcon },
|
||||||
{ name: 'Grafana', icon: grafanaIcon },
|
{ name: 'Grafana', icon: grafanaIcon },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export const INTERESTS = [
|
||||||
|
{
|
||||||
|
title: 'Travelling',
|
||||||
|
icon: <Plane size={32} />,
|
||||||
|
description:
|
||||||
|
'Exploring new places, experiencing different cultures, and creating lasting memories through adventures around the world. From scenic landscapes to bustling cities, every journey is an opportunity to learn and grow.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Photography',
|
||||||
|
icon: <Camera size={32} />,
|
||||||
|
description:
|
||||||
|
'Capturing moments and perspectives through the lens. Particularly interested in landscape and street photography, always looking to improve composition skills and trying new techniques.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Movies & Shows',
|
||||||
|
icon: <Film size={32} />,
|
||||||
|
description:
|
||||||
|
'Passionate about cinema across various genres and cultures. Enjoy analyzing cinematography, storytelling techniques, and discovering hidden gems from different parts of the world.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Homelab',
|
||||||
|
icon: <Server size={32} />,
|
||||||
|
description:
|
||||||
|
'Managing a personal homelab setup for experimenting with self-hosted services, networking configurations, and learning about system administration in a hands-on environment.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'New Technologies',
|
||||||
|
icon: <Cpu size={32} />,
|
||||||
|
description:
|
||||||
|
'Keeping up with the latest technological advancements, particularly in cloud computing, automation, and emerging DevOps tools. Enjoy experimenting with new frameworks and platforms.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Playing Video Games',
|
||||||
|
icon: <Gamepad2 size={32} />,
|
||||||
|
description:
|
||||||
|
'Enthusiastic gamer with a deep appreciation for interactive storytelling and virtual worlds. Enjoy exploring diverse genres from immersive RPGs to strategic multiplayer games.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Sports',
|
||||||
|
icon: <Trophy size={32} />,
|
||||||
|
description: 'Avid sports enthusiast following multiple disciplines:',
|
||||||
|
subInterests: [
|
||||||
|
{
|
||||||
|
name: 'Football',
|
||||||
|
details:
|
||||||
|
'Following major leagues and international tournaments, appreciating the tactical aspects and team dynamics of the beautiful game.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Cricket',
|
||||||
|
details:
|
||||||
|
'Enjoying both test matches and limited-overs formats, following international competitions and analyzing game strategies.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Formula 1',
|
||||||
|
icon: <Car size={24} />,
|
||||||
|
details:
|
||||||
|
'Following the high-speed world of F1, keeping up with team developments, race strategies, and technical innovations in motorsport.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export const EXPERIENCE = [
|
||||||
|
{
|
||||||
|
title: 'Experienced Developer (DevOps)',
|
||||||
|
company: 'Ericsson',
|
||||||
|
location: 'Jorvas, Finland',
|
||||||
|
period: 'November-2022 - Present',
|
||||||
|
responsibilities: [
|
||||||
|
'Managing and optimizing Kubernetes clusters in production environments',
|
||||||
|
'Designing and implementing CI/CD pipelines for end to end product development flow using Jenkins',
|
||||||
|
'Automating infrastructure deployment using Terraform and Ansible',
|
||||||
|
'Develop and maintain monitoring solutions of various resources for greater observability and troubleshooting',
|
||||||
|
'Actively support development teams regarding product development flow and infrastructure issues',
|
||||||
|
'Develop and perform automated end-to-end product testing with Python, Robot Framework, Jenkins, Bash, etc.',
|
||||||
|
],
|
||||||
|
tools: 'Kubernetes, Docker, KVM, Openstack, Ansible, Terraform, Prometheus, Grafana',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Test Engineer',
|
||||||
|
company: 'Nokia',
|
||||||
|
location: 'Espoo, Finland',
|
||||||
|
period: 'June-2021 - October-2022',
|
||||||
|
responsibilities: [
|
||||||
|
'Develop and maintain Cloud RAN E2E test setup for vCU and vDU application testing on top of RedHat Openshift',
|
||||||
|
'Develop automation and CI/CD flow for Cloud RAN testing using Python, Robot Framework, Bash, Jenkins etc.',
|
||||||
|
'Develop and perform automated testing to validate the functionality of Nokia Cloud RAN base stations',
|
||||||
|
'Integrate new hardware and software into the test setup',
|
||||||
|
'Perform hands on debugging and log analysis to nd root cause and solve any software or hardware issues',
|
||||||
|
],
|
||||||
|
tools: 'Keysight Nemo Outdoor, Nemo Analyze, Qualcomm PCAT, QCAT, QXDM',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Testing and Prototyping Intern',
|
||||||
|
company: 'GE Healthcare',
|
||||||
|
location: 'Helsinki, Finland',
|
||||||
|
period: 'Jan-2019 - May-2021',
|
||||||
|
responsibilities: [
|
||||||
|
'Planning, writing, and performing manual and automated tests of different prototype wireless medical devices',
|
||||||
|
'Designing driver and PCB circuits in Altium Designer to test the performance of the Digital Sensor Interface',
|
||||||
|
'Ensuring the PCB componets used in the devices are EU RoHS and REACH compliant',
|
||||||
|
],
|
||||||
|
tools:
|
||||||
|
'LTSpice, Altium Designer, HP-ALM, Vector Network Analyzer, Spectrum Analyzer, Climate Chamber',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export const EDUCATION = [
|
||||||
|
{
|
||||||
|
degree: "Master's in Wireless Communication & RF Systems",
|
||||||
|
institution: 'Tampere University',
|
||||||
|
location: 'Tampere, Finland',
|
||||||
|
period: '2018 - 2020',
|
||||||
|
thesis: '5G Reference Signals and their Possibility to be for 5G Based Positioning',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
degree: "Bachelor's in Electrical & Electronic Engineering",
|
||||||
|
institution: 'Khulna University of Engineering & Technology',
|
||||||
|
location: 'Khulna, Bangladesh',
|
||||||
|
period: '2013 - 2017',
|
||||||
|
thesis:
|
||||||
|
'Density-based smart traffic control system using Canny edge detection technique using Digital Image Processing',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|||||||
@ -1,70 +1,7 @@
|
|||||||
import { Building2, Calendar, GraduationCap, MapPin, Microscope, Wrench } from 'lucide-react'
|
import { Building2, Calendar, GraduationCap, MapPin, Microscope, Wrench } from 'lucide-react'
|
||||||
import { COLORS } from '../constants'
|
import { COLORS, EXPERIENCE, EDUCATION } from '../constants'
|
||||||
|
|
||||||
const Experience = () => {
|
const Experience = () => {
|
||||||
const experiences = [
|
|
||||||
{
|
|
||||||
title: 'Experienced Developer (DevOps)',
|
|
||||||
company: 'Ericsson',
|
|
||||||
location: 'Jorvas, Finland',
|
|
||||||
period: 'November-2022 - Present',
|
|
||||||
responsibilities: [
|
|
||||||
'Managing and optimizing Kubernetes clusters in production environments',
|
|
||||||
'Designing and implementing CI/CD pipelines for end to end product development flow using Jenkins',
|
|
||||||
'Automating infrastructure deployment using Terraform and Ansible',
|
|
||||||
'Develop and maintain monitoring solutions of various resources for greater observability and troubleshooting',
|
|
||||||
'Actively support development teams regarding product development flow and infrastructure issues',
|
|
||||||
'Develop and perform automated end-to-end product testing with Python, Robot Framework, Jenkins, Bash, etc.',
|
|
||||||
],
|
|
||||||
tools: 'Kubernetes, Docker, KVM, Openstack, Ansible, Terraform, Prometheus, Grafana',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Test Engineer',
|
|
||||||
company: 'Nokia',
|
|
||||||
location: 'Espoo, Finland',
|
|
||||||
period: 'June-2021 - October-2022',
|
|
||||||
responsibilities: [
|
|
||||||
'Develop and maintain Cloud RAN E2E test setup for vCU and vDU application testing on top of RedHat Openshift',
|
|
||||||
'Develop automation and CI/CD flow for Cloud RAN testing using Python, Robot Framework, Bash, Jenkins etc.',
|
|
||||||
'Develop and perform automated testing to validate the functionality of Nokia Cloud RAN base stations',
|
|
||||||
'Integrate new hardware and software into the test setup',
|
|
||||||
'Perform hands on debugging and log analysis to nd root cause and solve any software or hardware issues',
|
|
||||||
],
|
|
||||||
tools: 'Keysight Nemo Outdoor, Nemo Analyze, Qualcomm PCAT, QCAT, QXDM',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Testing and Prototyping Intern',
|
|
||||||
company: 'GE Healthcare',
|
|
||||||
location: 'Helsinki, Finland',
|
|
||||||
period: 'Jan-2019 - May-2021',
|
|
||||||
responsibilities: [
|
|
||||||
'Planning, writing, and performing manual and automated tests of different prototype wireless medical devices',
|
|
||||||
'Designing driver and PCB circuits in Altium Designer to test the performance of the Digital Sensor Interface',
|
|
||||||
'Ensuring the PCB componets used in the devices are EU RoHS and REACH compliant',
|
|
||||||
],
|
|
||||||
tools:
|
|
||||||
'LTSpice, Altium Designer, HP-ALM, Vector Network Analyzer, Spectrum Analyzer, Climate Chamber',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const education = [
|
|
||||||
{
|
|
||||||
degree: "Master's in Wireless Communication & RF Systems",
|
|
||||||
institution: 'Tampere University',
|
|
||||||
location: 'Tampere, Finland',
|
|
||||||
period: '2018 - 2020',
|
|
||||||
thesis: '5G Reference Signals and their Possibility to be for 5G Based Positioning',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
degree: "Bachelor's in Electrical & Electronic Engineering",
|
|
||||||
institution: 'Khulna University of Engineering & Technology',
|
|
||||||
location: 'Khulna, Bangladesh',
|
|
||||||
period: '2013 - 2017',
|
|
||||||
thesis:
|
|
||||||
'Density-based smart traffic control system using Canny edge detection technique using Digital Image Processing',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4 max-w-4xl mx-auto">
|
<div className="p-4 max-w-4xl mx-auto">
|
||||||
<h2
|
<h2
|
||||||
@ -74,7 +11,7 @@ const Experience = () => {
|
|||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
{experiences.map((exp, index) => (
|
{EXPERIENCE.map((exp, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className={`border-l-4 ${COLORS.BORDER} ${COLORS.DARK_BORDER} pl-4 space-y-2`}
|
className={`border-l-4 ${COLORS.BORDER} ${COLORS.DARK_BORDER} pl-4 space-y-2`}
|
||||||
@ -128,7 +65,7 @@ const Experience = () => {
|
|||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{education.map((edu, index) => (
|
{EDUCATION.map((edu, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className={`border-l-4 ${COLORS.BORDER} ${COLORS.DARK_BORDER} pl-4 space-y-2`}
|
className={`border-l-4 ${COLORS.BORDER} ${COLORS.DARK_BORDER} pl-4 space-y-2`}
|
||||||
|
|||||||
@ -1,69 +1,6 @@
|
|||||||
import { Camera, Plane, Film, Server, Cpu, Trophy, Car, Gamepad2 } from 'lucide-react'
|
import { COLORS, INTERESTS } from '../constants'
|
||||||
import { COLORS } from '../constants'
|
|
||||||
|
|
||||||
const Interests = () => {
|
const Interests = () => {
|
||||||
const interests = [
|
|
||||||
{
|
|
||||||
title: 'Travelling',
|
|
||||||
icon: <Plane size={32} />,
|
|
||||||
description:
|
|
||||||
'Exploring new places, experiencing different cultures, and creating lasting memories through adventures around the world. From scenic landscapes to bustling cities, every journey is an opportunity to learn and grow.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Photography',
|
|
||||||
icon: <Camera size={32} />,
|
|
||||||
description:
|
|
||||||
'Capturing moments and perspectives through the lens. Particularly interested in landscape and street photography, always looking to improve composition skills and trying new techniques.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Movies & Shows',
|
|
||||||
icon: <Film size={32} />,
|
|
||||||
description:
|
|
||||||
'Passionate about cinema across various genres and cultures. Enjoy analyzing cinematography, storytelling techniques, and discovering hidden gems from different parts of the world.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Homelab',
|
|
||||||
icon: <Server size={32} />,
|
|
||||||
description:
|
|
||||||
'Managing a personal homelab setup for experimenting with self-hosted services, networking configurations, and learning about system administration in a hands-on environment.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'New Technologies',
|
|
||||||
icon: <Cpu size={32} />,
|
|
||||||
description:
|
|
||||||
'Keeping up with the latest technological advancements, particularly in cloud computing, automation, and emerging DevOps tools. Enjoy experimenting with new frameworks and platforms.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Playing Video Games',
|
|
||||||
icon: <Gamepad2 size={32} />,
|
|
||||||
description:
|
|
||||||
'Enthusiastic gamer with a deep appreciation for interactive storytelling and virtual worlds. Enjoy exploring diverse genres from immersive RPGs to strategic multiplayer games.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Sports',
|
|
||||||
icon: <Trophy size={32} />,
|
|
||||||
description: 'Avid sports enthusiast following multiple disciplines:',
|
|
||||||
subInterests: [
|
|
||||||
{
|
|
||||||
name: 'Football',
|
|
||||||
details:
|
|
||||||
'Following major leagues and international tournaments, appreciating the tactical aspects and team dynamics of the beautiful game.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Cricket',
|
|
||||||
details:
|
|
||||||
'Enjoying both test matches and limited-overs formats, following international competitions and analyzing game strategies.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Formula 1',
|
|
||||||
icon: <Car size={24} />,
|
|
||||||
details:
|
|
||||||
'Following the high-speed world of F1, keeping up with team developments, race strategies, and technical innovations in motorsport.',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4 max-w-4xl mx-auto">
|
<div className="p-4 max-w-4xl mx-auto">
|
||||||
<h1
|
<h1
|
||||||
@ -73,7 +10,7 @@ const Interests = () => {
|
|||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div className="grid gap-6 md:grid-cols-2">
|
<div className="grid gap-6 md:grid-cols-2">
|
||||||
{interests.map((interest, index) => (
|
{INTERESTS.map((interest, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className={`group p-6 rounded-lg border-2 border-gray-300 dark:border-gray-700 hover:shadow-lg transition-all duration-300 ${
|
className={`group p-6 rounded-lg border-2 border-gray-300 dark:border-gray-700 hover:shadow-lg transition-all duration-300 ${
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/Introduction.tsx","./src/components/Navbar.tsx","./src/components/Photo.tsx","./src/components/Skills.tsx"],"version":"5.6.2"}
|
{"root":["./src/App.tsx","./src/constants.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/Footer.tsx","./src/components/Introduction.tsx","./src/components/Navbar.tsx","./src/components/Photo.tsx","./src/components/Sidebar.tsx","./src/components/Skills.tsx","./src/components/Tooltip.tsx","./src/pages/Experience.tsx","./src/pages/Home.tsx","./src/pages/Interests.tsx","./src/pages/Projects.tsx"],"version":"5.9.2"}
|
||||||
@ -1 +1 @@
|
|||||||
{"root":["./vite.config.ts"],"version":"5.6.2"}
|
{"root":["./vite.config.ts"],"version":"5.9.2"}
|
||||||
144
scripts/create_kubeconfig.sh
Executable file
144
scripts/create_kubeconfig.sh
Executable file
@ -0,0 +1,144 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
function usage() {
|
||||||
|
echo "--namespace <namespace> --user <user> --kubeconfig <kubeconfig>"
|
||||||
|
}
|
||||||
|
|
||||||
|
function create_namespace() {
|
||||||
|
local namespace="$1"
|
||||||
|
kubectl create namespace "$namespace" \
|
||||||
|
--dry-run=client -o yaml | kubectl apply -f -
|
||||||
|
}
|
||||||
|
|
||||||
|
function create_service_account() {
|
||||||
|
local user="$1"
|
||||||
|
local namespace="$2"
|
||||||
|
|
||||||
|
kubectl create serviceaccount "$user" \
|
||||||
|
--namespace "$namespace" \
|
||||||
|
--dry-run=client -o yaml | kubectl apply -f -
|
||||||
|
|
||||||
|
# Create associated secret
|
||||||
|
kubectl apply -f - <<EOF
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: ${user}-secret
|
||||||
|
namespace: ${namespace}
|
||||||
|
annotations:
|
||||||
|
kubernetes.io/service-account.name: ${user}
|
||||||
|
type: kubernetes.io/service-account-token
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "Service account $user created in namespace $namespace"
|
||||||
|
}
|
||||||
|
|
||||||
|
function create_role() {
|
||||||
|
local user="$1"
|
||||||
|
local namespace="$2"
|
||||||
|
|
||||||
|
kubectl create role "$user" \
|
||||||
|
--verb=get,list,watch,create,update,delete,patch,exec \
|
||||||
|
--resource=pods,pods/exec,services,deployments,secrets,configmaps \
|
||||||
|
--namespace "$namespace" \
|
||||||
|
--dry-run=client -o yaml | kubectl apply -f -
|
||||||
|
|
||||||
|
echo "Role $user created in namespace $namespace"
|
||||||
|
}
|
||||||
|
|
||||||
|
function create_role_binding() {
|
||||||
|
local user="$1"
|
||||||
|
local namespace="$2"
|
||||||
|
|
||||||
|
kubectl create rolebinding "$user" \
|
||||||
|
--role="$user" \
|
||||||
|
--serviceaccount="$namespace:$user" \
|
||||||
|
--namespace "$namespace" \
|
||||||
|
--dry-run=client -o yaml | kubectl apply -f -
|
||||||
|
|
||||||
|
echo "Role binding $user created in namespace $namespace"
|
||||||
|
}
|
||||||
|
|
||||||
|
function create_kubeconfig() {
|
||||||
|
local user="$1"
|
||||||
|
local namespace="$2"
|
||||||
|
local kubeconfig="$3"
|
||||||
|
|
||||||
|
SECRET_NAME=${user}-secret
|
||||||
|
CLUSTER_NAME=$(kubectl config view --minify -o jsonpath='{.clusters[0].name}')
|
||||||
|
TOKEN=$(kubectl get secret "$SECRET_NAME" -n "$namespace" -o jsonpath='{.data.token}' | base64 --decode)
|
||||||
|
CA=$(kubectl get secret "$SECRET_NAME" -n "$namespace" -o jsonpath='{.data.ca\.crt}')
|
||||||
|
|
||||||
|
SERVER=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')
|
||||||
|
|
||||||
|
# Create kubeconfig file with proper indentation
|
||||||
|
cat >"${kubeconfig}" <<EOF
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Config
|
||||||
|
clusters:
|
||||||
|
- name: ${CLUSTER_NAME}
|
||||||
|
cluster:
|
||||||
|
server: ${SERVER}
|
||||||
|
certificate-authority-data: ${CA}
|
||||||
|
contexts:
|
||||||
|
- name: ${CLUSTER_NAME}-${namespace}-ci
|
||||||
|
context:
|
||||||
|
cluster: ${CLUSTER_NAME}
|
||||||
|
namespace: ${namespace}
|
||||||
|
user: ${user}
|
||||||
|
current-context: ${CLUSTER_NAME}-${namespace}-ci
|
||||||
|
users:
|
||||||
|
- name: ${user}
|
||||||
|
user:
|
||||||
|
token: ${TOKEN}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "Kubeconfig file created at ${kubeconfig}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main script
|
||||||
|
function main() {
|
||||||
|
local namespace=""
|
||||||
|
local user=""
|
||||||
|
local kubeconfig=""
|
||||||
|
|
||||||
|
while [[ "$#" -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
-n|--namespace)
|
||||||
|
namespace="$2"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-u|--user)
|
||||||
|
user="$2"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-k|--kubeconfig)
|
||||||
|
kubeconfig="$2"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "$namespace" || -z "$user" || -z "$kubeconfig" ]]; then
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
create_namespace "$namespace"
|
||||||
|
create_service_account "$user" "$namespace"
|
||||||
|
create_role "$user" "$namespace"
|
||||||
|
create_role_binding "$user" "$namespace"
|
||||||
|
create_kubeconfig "$user" "$namespace" "$kubeconfig"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Call the main function
|
||||||
|
main "$@"
|
||||||
Reference in New Issue
Block a user