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
8 changes: 7 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
.dockerignore
Dockerfile
*.md
*.properties
*.properties
.git
.gradle
**/.gradle
**/build
**/dist
**/node_modules
4 changes: 3 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ jobs:
if: github.event_name != 'pull_request' && (github.event.ref == 'refs/heads/develop' || startsWith(github.event.ref, 'refs/tags'))
uses: softprops/action-gh-release@v2.6.1
with:
files: cwms-data-api/build/libs/cwms-data-api-${{steps.version.outputs.version}}.war
files: |
cwms-data-api/build/libs/cwms-data-api-${{steps.version.outputs.version}}.war
cda-gui/build/libs/cwms-data-ui-${{steps.version.outputs.version}}.war
tag_name: ${{steps.version.outputs.version}}
generate_release_notes: true
token: ${{ secrets.token != null && secrets.token || secrets.GITHUB_TOKEN }}
Expand Down
3 changes: 2 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ By making a contribution to this project, I certify that:

## Getting Started

This project is intended to replace what is currently hosted at https://cwms-data.usace.army.mil/cwms-data/
This project provides the UI hosted at https://cwms-data.usace.army.mil/ and the API hosted under
https://cwms-data.usace.army.mil/cwms-data/ as separate web applications.

Due to the current limits on releasing about database source we don't expect much random contribution. However, we will be moving more of the logic into
this repository and any help on the formatting code and such will be greatly appreciated.
Expand Down
12 changes: 5 additions & 7 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
FROM gradle:8.5-jdk11 AS builder
USER $USER
RUN --mount=type=cache,target=/home/gradle/.gradle
WORKDIR /builddir
COPY . /builddir/
RUN apt update && apt install -y curl
Expand All @@ -12,7 +10,11 @@ SHELL ["/bin/bash", "-c"]
RUN . "$NVM_DIR/nvm.sh" && nvm install $NODE_VERSION && nvm use $NODE_VERSION
ENV NODE_PATH=$NVM_DIR/v$NODE_VERSION/lib/node_modules
ENV PATH=$NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH
RUN gradle prepareDockerBuild --info --no-daemon
# Docker build contexts created from Git worktrees do not include the main
# repository's Git metadata. Use a deterministic build-only version rather
# than asking the Gradle Git version plugin to resolve an unavailable worktree.
RUN --mount=type=cache,target=/home/gradle/.gradle \
gradle prepareDockerBuild --info --no-daemon -PversionOverride=docker

FROM alpine:3.21.3 AS tomcat_base
RUN apk --no-cache upgrade && \
Expand All @@ -31,9 +33,6 @@ RUN mkdir /download && \
mv apache-tomcat-9.0.118 /usr/local/tomcat/ && \
cd / && \
rm -rf /usr/local/tomcat/webapps/* && \
mkdir /usr/local/tomcat/webapps/ROOT && \
printf "<%% response.sendRedirect(\"/cwms-data/\"); %%>\n" > /usr/local/tomcat/webapps/ROOT/index.jsp && \
printf "User-agent: *\nAllow: /cwms-data/\nDisallow: /cwms-data/auth/\nDisallow: /cwms-data/catalog/\nDisallow: /cwms-data/timeseries/\nDisallow: /cwms-data/swagger-docs\nDisallow: /auth/\nSitemap: https://cwms-data.usace.army.mil/sitemap.xml\n" > /usr/local/tomcat/webapps/ROOT/robots.txt && \
mkdir -p /usr/local/tomcat/conf/Catalina/localhost
# Now replace the Tomcat logging with logback
# NOTE: I have reviewed this jar in jd-gui and do not see anything malicious, packages are isolated to avoid issues
Expand All @@ -52,7 +51,6 @@ CMD ["/usr/local/tomcat/bin/catalina.sh","run"]

FROM tomcat_base AS api

COPY --from=builder /builddir/cda-gui/dist/sitemap.xml /usr/local/tomcat/webapps/ROOT/sitemap.xml
COPY --from=builder /builddir/cwms-data-api/build/docker/cda/ /usr/local/tomcat
COPY --from=builder /builddir/cwms-data-api/build/docker/context.xml /usr/local/tomcat/conf
COPY --from=builder /builddir/cwms-data-api/build/docker/server.xml /usr/local/tomcat/conf
Expand Down
5 changes: 3 additions & 2 deletions RELEASE_DEPLOY.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ portions):

The actual release will consist of the following elements:

1. A war file available under the (releases)[https://github.com/usace/cwms-data-api/releases] page
1. Two WAR files available under the [releases](https://github.com/usace/cwms-data-api/releases) page:
1. `cwms-data-api-<version>.war`, deployed as `cwms-data.war`, provides the API at `/cwms-data`.
2. `cwms-data-ui-<version>.war`, deployed as `ROOT.war`, provides the UI at `/`.
2. OCI container images available at `docker pull ghcr.io/usace/cwms-data-api:<tag>`
1. Note that there may be multiple tags for the same image, use what is appropriate to your current operation.

Expand Down Expand Up @@ -126,4 +128,3 @@ You can monitor the progress of the action from the new instances that will show
As this is just pushing images to the registry it *should* be fast. However, the actual activation by the environment is not
deterministic, this action only pushes the images. It does not verify if anything actually changed or worked. You will have to use the
monitoring tools appropriate to the given environment to determine if the new version is getting used.

10 changes: 6 additions & 4 deletions cda-gui.README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,18 @@ You can run the CDA GUI on your local system without tomcat.

#### Quick Start

1. `cd gda-gui`
1. `cd cda-gui`
2. `npm install` (NodeJS Required)
3. `npm run dev`
4. Open https://localhost:5173/cwms-data
4. Open http://localhost:5173/

Changes will now be hot reloaded in the browser as you update your React files.

### Deployed Locally in the WAR
### Deployed WAR

- 🚧 WIP
The GUI and API are packaged as separate applications. Deploy the GUI WAR as
`ROOT.war` to serve the UI at `/`, and deploy the API WAR as `cwms-data.war`
to serve API endpoints at `/cwms-data`.

## Helpful Tips

Expand Down
1 change: 1 addition & 0 deletions cda-gui/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build/
25 changes: 13 additions & 12 deletions cda-gui/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import com.github.gradle.node.npm.task.NpmTask
import com.github.gradle.node.npm.task.NpxTask

plugins {
id 'java'
id 'cda.deps-conventions'
id 'cda.java-conventions'
id 'war'
id 'com.github.node-gradle.node' version '7.1.0'
}

Expand All @@ -20,6 +22,13 @@ def cdaGuiViteMode = providers.gradleProperty('cdaGuiMode')
.orElse(providers.environmentVariable('CDA_GUI_MODE'))
.orElse('production')

dependencies {
compileOnly(libs.javaee.web.api)
testImplementation(libs.javaee.web.api)
testImplementation(libs.bundles.junit)
testImplementation(libs.mockito.core)
}

task buildGuiVite(type:NpxTask) {
dependsOn npmInstall
command = "vite"
Expand Down Expand Up @@ -62,16 +71,8 @@ task runGuiDev(type: Exec) {
workingDir project.layout.projectDirectory
}

sourceSets {
java {
main {
resources {
srcDir(project.layout.projectDirectory.dir('dist'))
}
}
}
}

tasks.named('processResources') {
war {
dependsOn buildGui
archiveBaseName = 'cwms-data-ui'
from(project.layout.projectDirectory.dir('dist'))
}
8 changes: 8 additions & 0 deletions cda-gui/public/robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
User-agent: *
Allow: /
Disallow: /auth/
Disallow: /cwms-data/auth/
Disallow: /cwms-data/catalog/
Disallow: /cwms-data/timeseries/
Disallow: /cwms-data/swagger-docs
Sitemap: https://cwms-data.usace.army.mil/sitemap.xml
2 changes: 1 addition & 1 deletion cda-gui/scripts/generate-sitemap.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const outputPath = path.join(projectDir, "dist", "sitemap.xml");
const siteOrigin = (
process.env.SITE_ORIGIN ?? "https://cwms-data.usace.army.mil"
).replace(/\/+$/, "");
const siteBasePath = (process.env.SITE_BASE_PATH ?? "/cwms-data").replace(/\/+$/, "");
const siteBasePath = (process.env.SITE_BASE_PATH ?? "").replace(/\/+$/, "");

const urls = sitemapPaths.map((routePath) => {
const normalizedPath = routePath ? `/${routePath}` : "";
Expand Down
2 changes: 1 addition & 1 deletion cda-gui/src/components/GlobalErrorBoundary.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export default class GlobalErrorBoundary extends Component {
<p className="mt-2 text-slate-700">
{this.state.error?.message ?? "An unexpected error occurred."}
</p>
<a className="mt-4 inline-block text-blue-700 underline" href="/cwms-data/">
<a className="mt-4 inline-block text-blue-700 underline" href="/">
Return to CDA
</a>
</main>
Expand Down
4 changes: 3 additions & 1 deletion cda-gui/src/links/header-links.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { getBasePath } from "../utils/base";

export default [
{
id: "home",
Expand All @@ -17,7 +19,7 @@ export default [
{
id: "swagger-schema",
text: "Swagger Docs Schema",
href: "/swagger-docs",
href: `${getBasePath()}/swagger-docs`,
},
],
},
Expand Down
2 changes: 1 addition & 1 deletion cda-gui/src/main.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const router = createBrowserRouter(
],
},
],
{ basename: "/cwms-data" },
{ basename: "/" },
);

ReactDOM.createRoot(document.getElementById("root")).render(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cwms.cda.servlet;
package cwms.cda.ui;

import java.io.IOException;
import java.util.Set;
Expand Down
13 changes: 13 additions & 0 deletions cda-gui/src/main/webapp/WEB-INF/web.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<display-name>CWMS Data API UI</display-name>

<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>

<!-- Client-side routing for React. The error-dispatch filter preserves 404 for unknown routes. -->
<error-page>
<error-code>404</error-code>
<location>/index.html</location>
</error-page>
</web-app>
2 changes: 1 addition & 1 deletion cda-gui/src/pages/swagger-ui/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export default function SwaggerUI() {
const cwmsAuthMethod = useMemo(() => {
const basePath = getBasePath();
return createCwmsLoginAuthMethod({
authUrl: `${window.location.origin}/CWMSLogin`,
authUrl: `${basePath}/CWMSLogin`,
authCheckUrl: `${basePath}/auth/keys`,
});
}, []);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cwms.cda.servlet;
package cwms.cda.ui;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.mockito.Mockito.mock;
Expand Down Expand Up @@ -43,7 +43,7 @@ void registersForIndexErrorDispatches() {
"/user-lists"
})
void returnsOkForClientRoutes(String route) throws ServletException, IOException {
HttpServletRequest request = buildRequest("GET", "/cwms-data" + route);
HttpServletRequest request = buildRequest("GET", route);
HttpServletResponse response = mock(HttpServletResponse.class);
FilterChain chain = mock(FilterChain.class);

Expand All @@ -55,7 +55,7 @@ void returnsOkForClientRoutes(String route) throws ServletException, IOException

@Test
void returnsOkForHeadRequest() throws ServletException, IOException {
HttpServletRequest request = buildRequest("HEAD", "/cwms-data/swagger-ui");
HttpServletRequest request = buildRequest("HEAD", "/swagger-ui");
HttpServletResponse response = mock(HttpServletResponse.class);
FilterChain chain = mock(FilterChain.class);

Expand All @@ -66,8 +66,8 @@ void returnsOkForHeadRequest() throws ServletException, IOException {
}

@Test
void returnsOkForAlternateContextPath() throws ServletException, IOException {
HttpServletRequest request = buildRequest("GET", "/spk-data/swagger-ui", "/spk-data");
void supportsNonRootContextPath() throws ServletException, IOException {
HttpServletRequest request = buildRequest("GET", "/cwms-data-ui/swagger-ui", "/cwms-data-ui");
HttpServletResponse response = mock(HttpServletResponse.class);
FilterChain chain = mock(FilterChain.class);

Expand All @@ -79,7 +79,7 @@ void returnsOkForAlternateContextPath() throws ServletException, IOException {

@Test
void preservesNotFoundStatusForUnknownRoutes() throws ServletException, IOException {
HttpServletRequest request = buildRequest("GET", "/cwms-data/not-a-client-route");
HttpServletRequest request = buildRequest("GET", "/not-a-client-route");
HttpServletResponse response = mock(HttpServletResponse.class);
FilterChain chain = mock(FilterChain.class);

Expand All @@ -91,7 +91,7 @@ void preservesNotFoundStatusForUnknownRoutes() throws ServletException, IOExcept

@Test
void preservesNotFoundStatusForNonPageRequests() throws ServletException, IOException {
HttpServletRequest request = buildRequest("POST", "/cwms-data/swagger-ui");
HttpServletRequest request = buildRequest("POST", "/swagger-ui");
HttpServletResponse response = mock(HttpServletResponse.class);
FilterChain chain = mock(FilterChain.class);

Expand All @@ -102,7 +102,7 @@ void preservesNotFoundStatusForNonPageRequests() throws ServletException, IOExce
}

private HttpServletRequest buildRequest(String method, String requestUri) {
return buildRequest(method, requestUri, "/cwms-data");
return buildRequest(method, requestUri, "");
}

private HttpServletRequest buildRequest(String method, String requestUri, String contextPath) {
Expand Down
5 changes: 2 additions & 3 deletions cda-gui/src/utils/base.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
// base.js
export function getBasePath() {
const { pathname } = window.location;
const basePath = "/" + pathname.split("/")[1];
return basePath;
const configuredRoot = import.meta.env.VITE_CDA_API_ROOT || "/cwms-data";
return new URL(configuredRoot, window.location.origin).pathname.replace(/\/$/, "");
}

export function getOrigin() {
Expand Down
3 changes: 1 addition & 2 deletions cda-gui/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ export default defineConfig(({ mode }) => {
env.VITE_CDA_API_ROOT || env.CDA_API_ROOT || "http://localhost:8081",
"http://localhost:8081",
).origin;
// const BASE_PATH = env?.BASE_PATH ?? "/cwms-data";
return {
base: "/cwms-data",
base: "/",
plugins: [react()],
server: {
proxy: {
Expand Down
19 changes: 5 additions & 14 deletions cwms-data-api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ plugins {

configurations {
info
webjars
docker
baseLibs
tomcatLibs
Expand Down Expand Up @@ -183,9 +182,6 @@ dependencies {
testImplementation(libs.cwms.tomcat.auth)
testImplementation(libs.apache.freemarker)

webjars(project(":cda-gui"))


// override versions
implementation(libs.bundles.overrides)

Expand All @@ -194,17 +190,7 @@ dependencies {
implementation(libs.minio)
}

task extractWebJars(type: Copy) {
dependsOn configurations.webjars
from zipTree(configurations.webjars.singleFile)
into file("$buildDir/extra")
includeEmptyDirs false
includeEmptyDirs = false
}

war {
dependsOn extractWebJars
from "$buildDir/extra"
from "src/resources/"

manifest {
Expand Down Expand Up @@ -386,6 +372,11 @@ task prepareDockerBuild(type: Copy, dependsOn: war) {
into "cda/webapps"
rename(".*\\.war", "cwms-data.war")
}

from(project(":cda-gui").tasks.named("war")) {
into "cda/webapps"
rename(".*\\.war", "ROOT.war")
}
}

task generateTimeSeriesSamples(type: JavaExec) {
Expand Down
8 changes: 1 addition & 7 deletions cwms-data-api/src/main/webapp/WEB-INF/web.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,4 @@
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<!-- Client Side Routing for React -->
<error-page>
<error-code>404</error-code>
<location>/index.html</location>
</error-page>
</web-app>
</web-app>
2 changes: 1 addition & 1 deletion docker-compose.README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ The following users and permissions are available:
|----------------------------------------------|-----------|----------------|-----------------------------------------|-----------------------|
| [traefik]() | 8081 | 8081 | entry point - web traffic | http://localhost:8081 |
| db | | 1521 | oracle database | |
| [api](./cwms-data-api/src/docker/Dockerfile) | | 7000 | tomcat CWMS Data API | |
| [api](./Dockerfile) | | 7000 | Tomcat hosting the UI at `/` and API at `/cwms-data` as separate WARs | http://localhost:8081/ |
| [auth](./compose_files/keycloak/Dockerfile) | | 8080 | authentication-token service (keycloak) | |
| db_install | | | connects to db and installs CWMS schema | |
| db_webuser_ permissions | | | connects to db and sets permissions | |
Expand Down
Loading
Loading