Skip to content
Open
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
21 changes: 11 additions & 10 deletions src/components/CommunityImpact/CommunityImpactComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import CardSwap, { Card } from './cardSwap'
import Particles from './particleBG';
import CloudInfraDashboard from "./cloudInfraDashboard";
import Link from '@docusaurus/Link';
import { communityImpactData } from '@site/src/data/communityImpactData';


const ImpactItem = ({ title, count, imageSrc, users }) => (
Expand All @@ -27,28 +28,28 @@ export default function CommunityImpactComponent() {
color: "#FF9900",
svg: <img src={useBaseUrl("/img/logos/corp/aws-black.svg")} alt="AWS Logo" />,
stats: [
{ value: 24, bar: "38%", label: "Ongoing Projects" },
{ value: 69, bar: "17%", label: "Active Users" },
{ value: communityImpactData.aws.projects, bar: communityImpactData.aws.projectsBar, label: "Ongoing Projects" },
{ value: communityImpactData.aws.users, bar: communityImpactData.aws.usersBar, label: "Active Users" },
],
},
{
accent: "gcp",
title: "GCP / JupyterHub",
title: "GCP + 2i2c JupyterHub",
color: "#4285F4",
svg: <img src={useBaseUrl("/img/logos/corp/google-cloud.jpg")} alt="GCP Logo" />,
stats: [
{ value: 63, bar: "100%", label: "Ongoing Projects" },
{ value: 183, bar: "45%", label: "Active Users" },
{ value: communityImpactData.gcp.projects, bar: communityImpactData.gcp.projectsBar, label: "Ongoing Projects" },
{ value: communityImpactData.gcp.users, bar: communityImpactData.gcp.usersBar, label: "Active Users" },
],
},
{
accent: "hpc",
title: "On-premise HPC",
title: "On-Premise HPC",
color: "#10B981",
svg: <img src={useBaseUrl("/img/logos/pantarhei.jpg")} alt="HPC Logo" />,
stats: [
{ value: 57, bar: "75%", label: "Ongoing Projects" },
{ value: 78, bar: "30%", label: "Active Users" },
{ value: communityImpactData.nsf.projects, bar: communityImpactData.nsf.projectsBar, label: "Ongoing Projects" },
{ value: communityImpactData.nsf.users, bar: communityImpactData.nsf.usersBar, label: "Active Users" },
],
},
{
Expand All @@ -57,8 +58,8 @@ export default function CommunityImpactComponent() {
color: "#8B5CF6",
svg: <img src={useBaseUrl("/img/logos/nsf-logo.png")} alt="NSF Logo" />,
stats: [
{ value: 7, bar: "50%", label: "Ongoing Projects" },
{ value: 75, bar: "60%", label: "Active Users" },
{ value: communityImpactData.nsf.projects, bar: communityImpactData.nsf.projectsBar, label: "Ongoing Projects" },
{ value: communityImpactData.nsf.users, bar: communityImpactData.nsf.usersBar, label: "Active Users" },
],
},
];
Expand Down
4 changes: 2 additions & 2 deletions src/components/CommunityImpact/cloudInfraDashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,9 @@ export default function CloudInfraDashboard({ cards = [] }) {
<div className="header">
<h1 className="tw-text-4xl tw-font-extrabold tw-text-center tw-mb-6
tw-text-black dark:tw-text-white">
Cloud Infrastructure
Research to Operations Hybrid Cloud (R2OHC)
</h1>
<p className="tw-text-gray-700 dark:tw-text-white">Real-time metrics across all platforms</p>
<p className="tw-text-gray-700 dark:tw-text-white">Last updated: September 2025</p>
</div>

<div className="cards-grid">
Expand Down
87 changes: 87 additions & 0 deletions src/components/HomepageFeatures/InfrastructureFeature/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import Link from "@docusaurus/Link";
import { communityImpactData } from '@site/src/data/communityImpactData';

export default function InfrastructureFeature() {

// ---------- PAGE CONTENT ----------
return (
<section className="tw-relative tw-overflow-hidden tw-py-24 tw-bg-slate-100 dark:tw-bg-slate-900 tw-text-blue-800 dark:tw-text-white tw-rounded-2xl tw-no-underline">
<div className="tw-container tw-mx-auto tw-flex tw-px-5 tw-items-center tw-justify-center tw-flex-col tw-relative tw-z-10">

<div className="tw-text-center tw-lg:w-2/3 tw-w-full">
<span className="tw-bg-blue-100 dark:tw-bg-blue-900/40 tw-text-blue-800 dark:tw-text-blue-300
tw-text-sm tw-font-semibold tw-px-4 tw-py-1.5 tw-rounded-full">
Research to Operations Hybrid Cloud
</span>

<h2 className="tw-text-5xl md:tw-text-6xl tw-font-extrabold tw-mb-6 tw-mt-4">
CIROH R2OHC
</h2>

<p className="tw-text-xl tw-max-w-2xl tw-text-slate-700 dark:tw-text-gray-300 tw-mx-auto">
CIROH's rich ecosystem of cloud-based and on-premises services empowers researchers to{" "}
<span className="tw-text-blue-700 dark:tw-text-cyan-400 tw-font-semibold">expand the capabilities</span>{" "}
of water modeling and forecasting.
</p>

<div className="tw-mt-12 tw-grid tw-grid-cols-4 md:tw-grid-cols-4 lg:tw-grid-cols-6 tw-gap-6 tw-max-w-5xl tw-mx-auto tw-items-center">

{/* Note: double-width grid is used to allow centering of elements for prime list size */}

<div className="tw-text-center tw-p-6 tw-bg-white dark:tw-bg-slate-800 tw-rounded-2xl tw-shadow-lg hover:tw-shadow-xl tw-transition-shadow tw-col-span-2">
<div className="tw-text-4xl tw-font-bold tw-text-black dark:tw-text-cyan-300">
104
</div>
<div className="tw-mt-2 tw-text-sm tw-font-semibold tw-text-gray-700 dark:tw-text-gray-300">
TOTAL PROJECTS
</div>
</div>

<div className="tw-text-center tw-p-6 tw-bg-white dark:tw-bg-slate-800 tw-rounded-2xl tw-shadow-lg hover:tw-shadow-xl tw-transition-shadow tw-col-span-2">
<div className="tw-text-4xl tw-font-bold tw-text-black dark:tw-text-cyan-300">
{communityImpactData.aws.projects}
</div>
<div className="tw-mt-2 tw-text-sm tw-font-semibold tw-text-gray-700 dark:tw-text-gray-300">
AWS PROJECTS
</div>
</div>

<div className="tw-text-center tw-p-6 tw-bg-white dark:tw-bg-slate-800 tw-rounded-2xl tw-shadow-lg hover:tw-shadow-xl tw-transition-shadow tw-col-span-2">
<div className="tw-text-4xl tw-font-bold tw-text-black dark:tw-text-cyan-300">
{communityImpactData.gcp.projects}
</div>
<div className="tw-mt-2 tw-text-sm tw-font-semibold tw-text-gray-700 dark:tw-text-gray-300">
GCP + 2i2c-JUPYTERHUB PROJECTS
</div>
</div>

<div className="tw-text-center tw-p-6 tw-bg-white dark:tw-bg-slate-800 tw-rounded-2xl tw-shadow-lg hover:tw-shadow-xl tw-transition-shadow tw-col-span-2 lg:tw-col-start-2">
<div className="tw-text-4xl tw-font-bold tw-text-black dark:tw-text-cyan-300">
{communityImpactData.hpc.projects}
</div>
<div className="tw-mt-2 tw-text-sm tw-font-semibold tw-text-gray-700 dark:tw-text-gray-300">
PANTARHEI/WUKONG HPC PROJECTS
</div>
</div>

<div className="tw-text-center tw-p-6 tw-bg-white dark:tw-bg-slate-800 tw-rounded-2xl tw-shadow-lg hover:tw-shadow-xl tw-transition-shadow tw-col-span-2 tw-col-start-2 lg:tw-col-start-4">
<div className="tw-text-4xl tw-font-bold tw-text-black dark:tw-text-cyan-300">
{communityImpactData.nsf.projects}
</div>
<div className="tw-mt-2 tw-text-sm tw-font-semibold tw-text-gray-700 dark:tw-text-gray-300">
NSF ACCESS PROJECTS
</div>
</div>

</div>

<div className="tw-mt-12 tw-grid tw-grid-cols-1 md:tw-grid-cols-1 lg:tw-grid-cols-2 tw-gap-6 tw-max-w-5xl tw-mx-auto">
<Link class="button button--active button--primary" to="/impact">Explore Community Impact</Link>
<Link class="button button--active button--primary" to="/docs/services/intro">Request IT Access</Link>
</div>

</div>
</div>
</section>
)
}
87 changes: 63 additions & 24 deletions src/components/HomepageFeatures/ResearchFeature/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,36 @@
import useBaseUrl from "@docusaurus/useBaseUrl";
import { fetchResourcesBySearch } from "@site/src/api/hydroshareAPI";
import { getCommunityResources } from "@site/src/components/HydroShareImporter";
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import React, { useState, useRef, useEffect } from 'react';


export default function ResearchFeature() {

// ---------- ZOTERO API HANDLING ----------
const { siteConfig } = useDocusaurusContext();
const zotero_api_key = siteConfig.customFields.zotero_api_key;
const zotero_group_id = siteConfig.customFields.zotero_group_id;
const zotero_params = {
sort: 'date',
direction: 'desc',
} // Exact params aren't too important, it's mostly a stub

// Mirrored from 'fetchTotal' in /src/components/Publications/index.js
async function zoteroCountPublications(groupId, apiKey, params, keyStr = '') {
const path = keyStr ? `/collections/${keyStr}/items/top` : '/items/top';

const url = new URL(`https://api.zotero.org/groups/${groupId}${path}`);
Object.entries({ ...params, limit: 1 }).forEach(([k, v]) =>
url.searchParams.append(k, v),
);

const resp = await fetch(url.href, { headers: { 'Zotero-API-Key': apiKey } });
if (!resp.ok) return null;
const hdr = resp.headers.get('Total-Results');
return hdr ? Number(hdr) : null;
}

// ---------- STATS STATE + KEYWORD-BASED FETCHING ----------
const [stats, setStats] = useState({
products: 0,
Expand All @@ -16,12 +43,12 @@ export default function ResearchFeature() {
const [statsError, setStatsError] = useState(null);

// Count resources for a single keyword, paging until exhausted
async function countByKeyword(keyword) {
async function hsCountByKeyword(keyword) {
let total = 0;
let page = 1;
let totalPagesChecked = 0;
try {
console.log(`[countByKeyword] Starting count for: ${keyword}`);
console.log(`[hsCountByKeyword] Starting count for: ${keyword}`);
while (true) {
const results = await fetchResourcesBySearch(
keyword,
Expand All @@ -35,38 +62,38 @@ export default function ResearchFeature() {
// Support wrappers that return array or { resources: [...] }
const items = Array.isArray(results) ? results : (results?.resources || []);

console.log(`[countByKeyword] Page ${page}: ${items.length} items returned for ${keyword}`);
console.log(`[hsCountByKeyword] Page ${page}: ${items.length} items returned for ${keyword}`);
totalPagesChecked++;

if (!items || items.length === 0) {
console.log(`[countByKeyword] No more items on page ${page}, stopping`);
console.log(`[hsCountByKeyword] No more items on page ${page}, stopping`);
break;
}

total += items.length;

// stop when a page is short (no more pages)
if (items.length < 40) {
console.log(`[countByKeyword] Short page (${items.length} < 40), stopping`);
console.log(`[hsCountByKeyword] Short page (${items.length} < 40), stopping`);
break;
}
page++;
}
console.log(`[countByKeyword] ${keyword}: Total ${total} resources across ${totalPagesChecked} pages`);
console.log(`[hsCountByKeyword] ${keyword}: Total ${total} resources across ${totalPagesChecked} pages`);
} catch (err) {
console.error(`[countByKeyword] ${keyword} error after ${totalPagesChecked} pages:`, err);
console.error(`[hsCountByKeyword] ${keyword} error after ${totalPagesChecked} pages:`, err);
throw err;
}
return total;
}

// Count community resources (matches datasets page behavior)
async function countCommunityResources(keyword, communityId = "4") {
async function hsCountCommunityResources(keyword, communityId = "4") {
let page = 1;
let totalPagesChecked = 0;
const resourceIds = new Set();
try {
console.log(`[countCommunityResources] Starting count for: ${keyword}`);
console.log(`[hsCountCommunityResources] Starting count for: ${keyword}`);
while (true) {
const response = await getCommunityResources(
keyword,
Expand All @@ -93,17 +120,17 @@ export default function ResearchFeature() {
response?.extraResourcesPageData?.hasMorePages
);

console.log(`[countCommunityResources] Page ${page}: ${items.length} items returned for ${keyword}`);
console.log(`[hsCountCommunityResources] Page ${page}: ${items.length} items returned for ${keyword}`);

if (!hasMorePages || items.length === 0) {
console.log(`[countCommunityResources] No more items on page ${page}, stopping`);
console.log(`[hsCountCommunityResources] No more items on page ${page}, stopping`);
break;
}
page++;
}
console.log(`[countCommunityResources] ${keyword}: Total ${resourceIds.size} resources across ${totalPagesChecked} pages`);
console.log(`[hsCountCommunityResources] ${keyword}: Total ${resourceIds.size} resources across ${totalPagesChecked} pages`);
} catch (err) {
console.error(`[countCommunityResources] ${keyword} error after ${totalPagesChecked} pages:`, err);
console.error(`[hsCountCommunityResources] ${keyword} error after ${totalPagesChecked} pages:`, err);
throw err;
}
return resourceIds.size;
Expand All @@ -117,22 +144,24 @@ export default function ResearchFeature() {
const datasetKeyword = "ciroh_portal_data,ciroh_hub_data";
const communityDatasetKeyword = datasetKeyword.split(",")[0].trim();

const [datasets, presentationsOld, coursesOld, productsOld, presentations, courses, products, notebooks] = await Promise.all([
countCommunityResources(communityDatasetKeyword, "4"),
countByKeyword("ciroh_portal_presentation"),
countByKeyword("nwm_portal_module"),
countByKeyword("nwm_portal_app"),
countByKeyword("ciroh_hub_presentation"),
countByKeyword("ciroh_hub_module"),
countByKeyword("ciroh_hub_app"),
countByKeyword("ciroh_hub_notebook"),
const [datasets, presentationsOld, coursesOld, productsOld, presentations, courses, products, notebooks, publications] = await Promise.all([
hsCountCommunityResources(communityDatasetKeyword, "4"),
hsCountByKeyword("ciroh_portal_presentation"),
hsCountByKeyword("nwm_portal_module"),
hsCountByKeyword("nwm_portal_app"),
hsCountByKeyword("ciroh_hub_presentation"),
hsCountByKeyword("ciroh_hub_module"),
hsCountByKeyword("ciroh_hub_app"),
hsCountByKeyword("ciroh_hub_notebook"),
zoteroCountPublications(zotero_group_id, zotero_api_key, zotero_params),
]);

setStats({
products: productsOld + products,
datasets: datasets,
presentations: presentationsOld + presentations,
courses: coursesOld + courses,
publications: publications,
notebooks: notebooks,
});
} catch (err) {
Expand Down Expand Up @@ -181,7 +210,7 @@ export default function ResearchFeature() {
</p>

{/* ---------- KEYWORD-BASED STATS ---------- */}
<div className="tw-mt-12 tw-grid tw-grid-cols-2 md:tw-grid-cols-3 lg:tw-grid-cols-5 tw-gap-6 tw-max-w-5xl tw-mx-auto">
<div className="tw-mt-12 tw-grid tw-grid-cols-2 md:tw-grid-cols-3 lg:tw-grid-cols-3 tw-gap-6 tw-max-w-5xl tw-mx-auto">

{/* PRODUCTS (APPLICATIONS) */}
<div className="tw-text-center tw-p-6 tw-bg-white dark:tw-bg-slate-800 tw-rounded-2xl tw-shadow-lg hover:tw-shadow-xl tw-transition-shadow">
Expand Down Expand Up @@ -213,6 +242,16 @@ export default function ResearchFeature() {
</div>
</div>

{/* NOTEBOOKS */}
<div className="tw-text-center tw-p-6 tw-bg-white dark:tw-bg-slate-800 tw-rounded-2xl tw-shadow-lg hover:tw-shadow-xl tw-transition-shadow">
<div className="tw-text-4xl tw-font-bold tw-text-black dark:tw-text-cyan-300">
{statsLoading ? <span className="tw-animate-ping">...</span> : stats.publications}
</div>
<div className="tw-mt-2 tw-text-sm tw-font-semibold tw-text-gray-700 dark:tw-text-gray-300">
PUBLICATIONS
</div>
</div>

{/* NOTEBOOKS */}
<div className="tw-text-center tw-p-6 tw-bg-white dark:tw-bg-slate-800 tw-rounded-2xl tw-shadow-lg hover:tw-shadow-xl tw-transition-shadow">
<div className="tw-text-4xl tw-font-bold tw-text-black dark:tw-text-cyan-300">
Expand All @@ -224,7 +263,7 @@ export default function ResearchFeature() {
</div>

{/* COURSES */}
<div className="tw-text-center tw-p-6 tw-bg-white dark:tw-bg-slate-800 tw-rounded-2xl tw-shadow-lg hover:tw-shadow-xl tw-transition-shadow tw-col-span-2 md:tw-col-span-1">
<div className="tw-text-center tw-p-6 tw-bg-white dark:tw-bg-slate-800 tw-rounded-2xl tw-shadow-lg hover:tw-shadow-xl tw-transition-shadow tw-col-span-1">
<div className="tw-text-4xl tw-font-bold tw-text-black dark:tw-text-cyan-300">
{statsLoading ? <span className="tw-animate-ping">...</span> : stats.courses}
</div>
Expand Down
3 changes: 3 additions & 0 deletions src/components/HomepageFeatures/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import styles from "./styles.module.css";
import ExploreFeature from './ExploreFeature';
import FeedbackFeature from './FeedbackFeature';
import ResearchFeature from './ResearchFeature';
import InfrastructureFeature from './InfrastructureFeature';
import TestimonialGallery from './TestimonialGallery';
import TeamGallery from './TeamGallery';
import OrgGallery from './OrgGallery';
Expand All @@ -17,6 +18,8 @@ export default function HomepageFeatures() {

<ExploreFeature />
<ResearchFeature />
<br /> {/* Protects against these panels melding together */}
<InfrastructureFeature />
<TeamGallery />
<FeedbackFeature />
<TestimonialGallery />
Expand Down
28 changes: 28 additions & 0 deletions src/data/communityImpactData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// TODO: Not clear how the bars on the community impact page are set.
// Are they just arbitrary values? If so, that's... extremely not ideal.
export const communityImpactData = {
aws: {
projects: 24,
projectsBar: "38%",
users: 69,
usersBar: "17%",
},
gcp: {
projects: 63,
projectsBar: "100%",
users: 183,
usersBar: "45%",
},
hpc: {
projects: 57,
projectsBar: "75%",
users: 78,
usersBar: "30%",
},
nsf: {
projects: 7,
projectsBar: "50%",
users: 75,
usersBar: "60%",
},
}