mirror of
https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit.git
synced 2025-12-14 06:35:15 +00:00
Compare commits
119 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 47b746c4b1 | |||
| 92fe759b0f | |||
| 356914f306 | |||
| 9f3748cc49 | |||
| f7063484d9 | |||
| b8014de7c3 | |||
| f4c457043a | |||
| dffe43beee | |||
| 3f1ac10290 | |||
| 85f980be0a | |||
| d37af01b3a | |||
| 6ae0e56118 | |||
| f1478561c1 | |||
| 236390e57b | |||
| 5725f8a22f | |||
| 57d05eab38 | |||
| 62ff8e8a28 | |||
| 91fdaa89f9 | |||
| 27f9a397b2 | |||
| a76180fd48 | |||
| ffeef91166 | |||
| 9cf99ec78d | |||
| 5f4e1b7207 | |||
| 9729004982 | |||
| 5a6f2072d7 | |||
| 189ba1d18c | |||
|
|
3474d4e7e5 | ||
|
|
f1f69ad32f | ||
|
|
b10d9a8cec | ||
|
|
5277cd16f4 | ||
|
|
850257f977 | ||
|
|
6826b7ba59 | ||
|
|
dac129d16c | ||
|
|
2af2fd9b11 | ||
|
|
777dddbcaf | ||
|
|
3a34212899 | ||
|
|
6447047b86 | ||
|
|
cebed936ae | ||
| 3744123361 | |||
|
|
7eaa94a705 | ||
| 399249faa3 | |||
| 26f095cdd2 | |||
|
|
ff366fa6ba | ||
|
|
d547ca12a1 | ||
|
|
8df84579a8 | ||
|
|
35221a7495 | ||
|
|
a6a8427eed | ||
|
|
ea9ae2abf3 | ||
|
|
505e78404e | ||
|
|
4c451962ae | ||
|
|
d740ac8b9e | ||
| 8e7a272ee7 | |||
|
|
8931e921be | ||
|
|
81d4f775e9 | ||
|
|
9a379e74f2 | ||
|
|
a5b442cd2a | ||
|
|
81919bface | ||
|
|
a78d33fd5f | ||
|
|
26685633ce | ||
|
|
3b75177a4c | ||
|
|
9a0d419e18 | ||
|
|
c4f5f2c1fd | ||
|
|
3e4982425d | ||
|
|
992ab6f168 | ||
|
|
1d9a263d23 | ||
|
|
ea402ceaa9 | ||
|
|
55856a35dc | ||
|
|
3413b9ba71 | ||
|
|
ebb8f31974 | ||
|
|
43921790fa | ||
|
|
83ec579214 | ||
|
|
0bb3ba7e82 | ||
|
|
410dd003f7 | ||
|
|
7d69ca27ae | ||
|
|
d662e8509a | ||
|
|
928a331a40 | ||
|
|
0674128568 | ||
|
|
ea5752f59b | ||
|
|
8d078e672b | ||
|
|
75639c176c | ||
|
|
3fe12a8030 | ||
|
|
e2a6b19a2e | ||
|
|
6ede139084 | ||
|
|
366917c768 | ||
| d38e29160c | |||
| 847a5b2a6d | |||
| 1bd387b848 | |||
| bf6c0887d6 | |||
| 5c559f9652 | |||
| fa091db463 | |||
| 0506d76ea9 | |||
| d9ac3dc05b | |||
| 431bfb5a46 | |||
| d2e05ee4a0 | |||
| 6412e537a8 | |||
| b6f9d2697e | |||
| 11e8569c56 | |||
| f150e7baaf | |||
| f91af1148d | |||
| 179982dedc | |||
| 5efae0c64e | |||
| f172b00737 | |||
| b08f4e5790 | |||
| 7368f42e8b | |||
| 7049a902a4 | |||
| 84b3df524f | |||
| 6bca03aa7c | |||
| ca8668e5e5 | |||
| ea97f49ead | |||
| cd06b1b216 | |||
| 2efe4cbb2f | |||
| dfa0ef31b2 | |||
| ca8613ff1b | |||
| 89ef0de20c | |||
| cfe4f3dd4f | |||
| 02d58facbb | |||
| 8f2c90087d | |||
| 3365255a5b | |||
| ebb691449b |
23
.github/ct/chart-schema.yaml
vendored
Normal file
23
.github/ct/chart-schema.yaml
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
name: str()
|
||||||
|
home: str()
|
||||||
|
version: str()
|
||||||
|
apiVersion: str()
|
||||||
|
appVersion: any(str(), num(), required=False)
|
||||||
|
type: str()
|
||||||
|
dependencies: any(required=False)
|
||||||
|
description: str()
|
||||||
|
keywords: list(str(), required=False)
|
||||||
|
sources: list(str(), required=False)
|
||||||
|
maintainers: list(include('maintainer'), required=False)
|
||||||
|
icon: str(required=False)
|
||||||
|
engine: str(required=False)
|
||||||
|
condition: str(required=False)
|
||||||
|
tags: str(required=False)
|
||||||
|
deprecated: bool(required=False)
|
||||||
|
kubeVersion: str(required=False)
|
||||||
|
annotations: map(str(), str(), required=False)
|
||||||
|
---
|
||||||
|
maintainer:
|
||||||
|
name: str()
|
||||||
|
email: str(required=False)
|
||||||
|
url: str(required=False)
|
||||||
15
.github/ct/config.yaml
vendored
Normal file
15
.github/ct/config.yaml
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
debug: true
|
||||||
|
remote: origin
|
||||||
|
chart-yaml-schema: .github/ct/chart-schema.yaml
|
||||||
|
validate-maintainers: false
|
||||||
|
validate-chart-schema: true
|
||||||
|
validate-yaml: true
|
||||||
|
check-version-increment: true
|
||||||
|
chart-dirs:
|
||||||
|
- charts
|
||||||
|
helm-extra-args: --timeout 300s
|
||||||
|
upgrade: true
|
||||||
|
skip-missing-values: true
|
||||||
|
release-label: release
|
||||||
|
release-name-template: "helm-v{{ .Version }}"
|
||||||
|
target-branch: master
|
||||||
84
.github/workflows/build-images.yaml
vendored
Normal file
84
.github/workflows/build-images.yaml
vendored
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
name: Build Container Images
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "image/v*"
|
||||||
|
paths-ignore:
|
||||||
|
- "charts/**"
|
||||||
|
pull_request:
|
||||||
|
branches: [master]
|
||||||
|
paths-ignore:
|
||||||
|
- "charts/**"
|
||||||
|
env:
|
||||||
|
IMAGES: docker.io/hapiproject/hapi
|
||||||
|
PLATFORMS: linux/amd64,linux/arm64/v8
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- name: Container meta for default (distroless) image
|
||||||
|
id: docker_meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ${{ env.IMAGES }}
|
||||||
|
tags: |
|
||||||
|
type=match,pattern=image/(.*),group=1,enable=${{github.event_name != 'pull_request'}}
|
||||||
|
|
||||||
|
|
||||||
|
- name: Container meta for tomcat image
|
||||||
|
id: docker_tomcat_meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ${{ env.IMAGES }}
|
||||||
|
tags: |
|
||||||
|
type=match,pattern=image/(.*),group=1,enable=${{github.event_name != 'pull_request'}}
|
||||||
|
flavor: |
|
||||||
|
suffix=-tomcat,onlatest=true
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Cache Docker layers
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: /tmp/.buildx-cache
|
||||||
|
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-buildx-
|
||||||
|
|
||||||
|
- name: Build and push default (distroless) image
|
||||||
|
id: docker_build
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
cache-from: type=local,src=/tmp/.buildx-cache
|
||||||
|
cache-to: type=local,dest=/tmp/.buildx-cache
|
||||||
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||||
|
platforms: ${{ env.PLATFORMS }}
|
||||||
|
target: default
|
||||||
|
|
||||||
|
- name: Build and push tomcat image
|
||||||
|
id: docker_build_tomcat
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
cache-from: type=local,src=/tmp/.buildx-cache
|
||||||
|
cache-to: type=local,dest=/tmp/.buildx-cache
|
||||||
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
tags: ${{ steps.docker_tomcat_meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.docker_tomcat_meta.outputs.labels }}
|
||||||
|
platforms: ${{ env.PLATFORMS }}
|
||||||
|
target: tomcat
|
||||||
41
.github/workflows/chart-release.yaml
vendored
Normal file
41
.github/workflows/chart-release.yaml
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
name: Release Charts
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- "charts/**"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- name: Add workspace as safe directory
|
||||||
|
run: |
|
||||||
|
git config --global --add safe.directory /__w/FHIRFLARE-IG-Toolkit/FHIRFLARE-IG-Toolkit
|
||||||
|
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Configure Git
|
||||||
|
run: |
|
||||||
|
git config user.name "$GITHUB_ACTOR"
|
||||||
|
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
|
||||||
|
|
||||||
|
- name: Update dependencies
|
||||||
|
run: find charts/ ! -path charts/ -maxdepth 1 -type d -exec helm dependency update {} \;
|
||||||
|
|
||||||
|
- name: Add Helm Repositories
|
||||||
|
run: |
|
||||||
|
helm repo add hapifhir https://hapifhir.github.io/hapi-fhir-jpaserver-starter/
|
||||||
|
helm repo update
|
||||||
|
|
||||||
|
- name: Run chart-releaser
|
||||||
|
uses: helm/chart-releaser-action@be16258da8010256c6e82849661221415f031968 # v1.5.0
|
||||||
|
with:
|
||||||
|
config: .github/ct/config.yaml
|
||||||
|
env:
|
||||||
|
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
73
.github/workflows/chart-test.yaml
vendored
Normal file
73
.github/workflows/chart-test.yaml
vendored
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
name: Lint and Test Charts
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
paths:
|
||||||
|
- "charts/**"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
container: quay.io/helmpack/chart-testing:v3.11.0@sha256:f2fd21d30b64411105c7eafb1862783236a219d29f2292219a09fe94ca78ad2a
|
||||||
|
steps:
|
||||||
|
- name: Install helm-docs
|
||||||
|
working-directory: /tmp
|
||||||
|
env:
|
||||||
|
HELM_DOCS_URL: https://github.com/norwoodj/helm-docs/releases/download/v1.14.2/helm-docs_1.14.2_Linux_x86_64.tar.gz
|
||||||
|
run: |
|
||||||
|
curl -LSs $HELM_DOCS_URL | tar xz && \
|
||||||
|
mv ./helm-docs /usr/local/bin/helm-docs && \
|
||||||
|
chmod +x /usr/local/bin/helm-docs && \
|
||||||
|
helm-docs --version
|
||||||
|
|
||||||
|
- name: Add workspace as safe directory
|
||||||
|
run: |
|
||||||
|
git config --global --add safe.directory /__w/hapi-fhir-jpaserver-starter/hapi-fhir-jpaserver-starter
|
||||||
|
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Check if documentation is up-to-date
|
||||||
|
run: helm-docs && git diff --exit-code HEAD
|
||||||
|
|
||||||
|
- name: Run chart-testing (lint)
|
||||||
|
run: ct lint --config .github/ct/config.yaml
|
||||||
|
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
k8s-version: [1.30.8, 1.31.4, 1.32.0]
|
||||||
|
needs:
|
||||||
|
- lint
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up chart-testing
|
||||||
|
uses: helm/chart-testing-action@e6669bcd63d7cb57cb4380c33043eebe5d111992 # v2.6.1
|
||||||
|
|
||||||
|
- name: Run chart-testing (list-changed)
|
||||||
|
id: list-changed
|
||||||
|
run: |
|
||||||
|
changed=$(ct list-changed --config .github/ct/config.yaml)
|
||||||
|
if [[ -n "$changed" ]]; then
|
||||||
|
echo "::set-output name=changed::true"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Create k8s Kind Cluster
|
||||||
|
uses: helm/kind-action@dda0770415bac9fc20092cacbc54aa298604d140 # v1.8.0
|
||||||
|
if: ${{ steps.list-changed.outputs.changed == 'true' }}
|
||||||
|
with:
|
||||||
|
cluster_name: kind-cluster-k8s-${{ matrix.k8s-version }}
|
||||||
|
node_image: kindest/node:v${{ matrix.k8s-version }}
|
||||||
|
|
||||||
|
- name: Run chart-testing (install)
|
||||||
|
run: ct install --config .github/ct/config.yaml
|
||||||
|
if: ${{ steps.list-changed.outputs.changed == 'true' }}
|
||||||
58
.github/workflows/docker-publish.yml
vendored
Normal file
58
.github/workflows/docker-publish.yml
vendored
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
# This workflow builds and pushes a multi-architecture Docker image to GitHub Container Registry (ghcr.io).
|
||||||
|
#
|
||||||
|
# The Docker meta step is required because GitHub repository names can contain uppercase letters, but Docker image tags must be lowercase.
|
||||||
|
# The docker/metadata-action@v5 normalizes the repository name to lowercase, ensuring the build and push steps use a valid image tag.
|
||||||
|
#
|
||||||
|
# This workflow builds for both AMD64 and ARM64 architectures using Docker Buildx and QEMU emulation.
|
||||||
|
|
||||||
|
name: Build and Push Docker image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- '*' # This will run the workflow on any branch
|
||||||
|
workflow_dispatch: # This enables manual triggering
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-push:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Log in to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ghcr.io/${{ github.repository }}
|
||||||
|
|
||||||
|
- name: Set normalized image name
|
||||||
|
run: |
|
||||||
|
if [[ "${{ github.ref_name }}" == "main" ]]; then
|
||||||
|
echo "IMAGE_NAME=$(echo ${{ steps.meta.outputs.tags }} | sed 's/:main/:latest/')" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "IMAGE_NAME=${{ steps.meta.outputs.tags }}" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Build and push multi-architecture Docker image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./docker/Dockerfile
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: true
|
||||||
|
tags: ${{ env.IMAGE_NAME }}
|
||||||
40
.github/workflows/jekyll.yml
vendored
Normal file
40
.github/workflows/jekyll.yml
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
name: Build and Deploy Jekyll site
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main, master ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main, master ]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: website
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Setup Ruby
|
||||||
|
uses: ruby/setup-ruby@v1
|
||||||
|
with:
|
||||||
|
ruby-version: '3.2'
|
||||||
|
bundler-cache: true
|
||||||
|
working-directory: website
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
bundle install
|
||||||
|
|
||||||
|
- name: Build site
|
||||||
|
run: bundle exec jekyll build
|
||||||
|
|
||||||
|
- name: Deploy to gh_pages branch
|
||||||
|
uses: peaceiris/actions-gh-pages@v4
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
publish_dir: ./website/_site
|
||||||
|
publish_branch: gh-pages
|
||||||
|
keep_files: true
|
||||||
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
/logs/
|
||||||
|
/.pydevproject
|
||||||
|
/__pycache__/
|
||||||
|
/myenv/
|
||||||
|
/tmp/
|
||||||
|
/Gemfile.lock
|
||||||
|
/_site/
|
||||||
23
.project
Normal file
23
.project
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>FHIRFLARE-IG-Toolkit</name>
|
||||||
|
<comment></comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.python.pydev.PyDevBuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.wst.validation.validationbuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>org.eclipse.wst.jsdt.core.jsNature</nature>
|
||||||
|
<nature>org.python.pydev.pythonNature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
||||||
7
.settings/.jsdtscope
Normal file
7
.settings/.jsdtscope
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<classpath>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.JRE_CONTAINER"/>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.baseBrowserLibrary"/>
|
||||||
|
<classpathentry kind="src" path=""/>
|
||||||
|
<classpathentry kind="output" path=""/>
|
||||||
|
</classpath>
|
||||||
1
.settings/org.eclipse.wst.jsdt.ui.superType.container
Normal file
1
.settings/org.eclipse.wst.jsdt.ui.superType.container
Normal file
@ -0,0 +1 @@
|
|||||||
|
org.eclipse.wst.jsdt.launching.JRE_CONTAINER
|
||||||
1
.settings/org.eclipse.wst.jsdt.ui.superType.name
Normal file
1
.settings/org.eclipse.wst.jsdt.ui.superType.name
Normal file
@ -0,0 +1 @@
|
|||||||
|
Global
|
||||||
@ -2,57 +2,130 @@
|
|||||||
setlocal enabledelayedexpansion
|
setlocal enabledelayedexpansion
|
||||||
|
|
||||||
REM --- Configuration ---
|
REM --- Configuration ---
|
||||||
set REPO_URL=https://github.com/hapifhir/hapi-fhir-jpaserver-starter.git
|
set REPO_URL_HAPI=https://github.com/hapifhir/hapi-fhir-jpaserver-starter.git
|
||||||
set CLONE_DIR=hapi-fhir-jpaserver
|
set REPO_URL_CANDLE=https://github.com/FHIR/fhir-candle.git
|
||||||
|
set CLONE_DIR_HAPI=hapi-fhir-jpaserver
|
||||||
|
set CLONE_DIR_CANDLE=fhir-candle
|
||||||
set SOURCE_CONFIG_DIR=hapi-fhir-setup
|
set SOURCE_CONFIG_DIR=hapi-fhir-setup
|
||||||
set CONFIG_FILE=application.yaml
|
set CONFIG_FILE=application.yaml
|
||||||
|
|
||||||
REM --- Define Paths ---
|
REM --- Define Paths ---
|
||||||
set SOURCE_CONFIG_PATH=..\%SOURCE_CONFIG_DIR%\target\classes\%CONFIG_FILE%
|
set SOURCE_CONFIG_PATH=..\%SOURCE_CONFIG_DIR%\target\classes\%CONFIG_FILE%
|
||||||
set DEST_CONFIG_PATH=%CLONE_DIR%\target\classes\%CONFIG_FILE%
|
set DEST_CONFIG_PATH=%CLONE_DIR_HAPI%\target\classes\%CONFIG_FILE%
|
||||||
|
|
||||||
REM === CORRECTED: Prompt for Version ===
|
REM --- NEW: Define a variable for the custom FHIR URL and server type ---
|
||||||
|
set "CUSTOM_FHIR_URL_VAL="
|
||||||
|
set "SERVER_TYPE="
|
||||||
|
set "CANDLE_FHIR_VERSION="
|
||||||
|
|
||||||
|
REM === MODIFIED: Prompt for Installation Mode ===
|
||||||
:GetModeChoice
|
:GetModeChoice
|
||||||
SET "APP_MODE=" REM Clear the variable first
|
SET "APP_MODE=" REM Clear the variable first
|
||||||
|
echo.
|
||||||
echo Select Installation Mode:
|
echo Select Installation Mode:
|
||||||
echo 1. Standalone (Includes local HAPI FHIR Server - Requires Git & Maven)
|
echo 1. Lite (Excludes local HAPI FHIR Server - No Git/Maven/Dotnet needed)
|
||||||
echo 2. Lite (Excludes local HAPI FHIR Server - No Git/Maven needed)
|
echo 2. Custom URL (Uses a custom FHIR Server - No Git/Maven/Dotnet needed)
|
||||||
CHOICE /C 12 /N /M "Enter your choice (1 or 2):"
|
echo 3. Hapi (Includes local HAPI FHIR Server - Requires Git & Maven)
|
||||||
|
echo 4. Candle (Includes local FHIR Candle Server - Requires Git & Dotnet)
|
||||||
|
CHOICE /C 1234 /N /M "Enter your choice (1, 2, 3, or 4):"
|
||||||
|
|
||||||
|
IF ERRORLEVEL 4 (
|
||||||
|
SET APP_MODE=standalone
|
||||||
|
SET SERVER_TYPE=candle
|
||||||
|
goto :GetCandleFhirVersion
|
||||||
|
)
|
||||||
|
IF ERRORLEVEL 3 (
|
||||||
|
SET APP_MODE=standalone
|
||||||
|
SET SERVER_TYPE=hapi
|
||||||
|
goto :ModeSet
|
||||||
|
)
|
||||||
IF ERRORLEVEL 2 (
|
IF ERRORLEVEL 2 (
|
||||||
|
SET APP_MODE=standalone
|
||||||
|
goto :GetCustomUrl
|
||||||
|
)
|
||||||
|
IF ERRORLEVEL 1 (
|
||||||
SET APP_MODE=lite
|
SET APP_MODE=lite
|
||||||
goto :ModeSet
|
goto :ModeSet
|
||||||
)
|
)
|
||||||
IF ERRORLEVEL 1 (
|
|
||||||
SET APP_MODE=standalone
|
|
||||||
goto :ModeSet
|
|
||||||
)
|
|
||||||
REM If somehow neither was chosen (e.g., Ctrl+C), loop back
|
REM If somehow neither was chosen (e.g., Ctrl+C), loop back
|
||||||
echo Invalid input. Please try again.
|
echo Invalid input. Please try again.
|
||||||
goto :GetModeChoice
|
goto :GetModeChoice
|
||||||
|
|
||||||
|
:GetCustomUrl
|
||||||
|
set "CONFIRMED_URL="
|
||||||
|
:PromptUrlLoop
|
||||||
|
echo.
|
||||||
|
set /p "CUSTOM_URL_INPUT=Please enter the custom FHIR server URL: "
|
||||||
|
echo.
|
||||||
|
echo You entered: !CUSTOM_URL_INPUT!
|
||||||
|
set /p "CONFIRM_URL=Is this URL correct? (Y/N): "
|
||||||
|
if /i "!CONFIRM_URL!" EQU "Y" (
|
||||||
|
set "CONFIRMED_URL=!CUSTOM_URL_INPUT!"
|
||||||
|
goto :ConfirmUrlLoop
|
||||||
|
) else (
|
||||||
|
goto :PromptUrlLoop
|
||||||
|
)
|
||||||
|
:ConfirmUrlLoop
|
||||||
|
echo.
|
||||||
|
echo Please re-enter the URL to confirm it is correct:
|
||||||
|
set /p "CUSTOM_URL_INPUT=Re-enter URL: "
|
||||||
|
if /i "!CUSTOM_URL_INPUT!" EQU "!CONFIRMED_URL!" (
|
||||||
|
set "CUSTOM_FHIR_URL_VAL=!CUSTOM_URL_INPUT!"
|
||||||
|
echo.
|
||||||
|
echo Custom URL confirmed: !CUSTOM_FHIR_URL_VAL!
|
||||||
|
goto :ModeSet
|
||||||
|
) else (
|
||||||
|
echo.
|
||||||
|
echo URLs do not match. Please try again.
|
||||||
|
goto :PromptUrlLoop
|
||||||
|
)
|
||||||
|
|
||||||
|
:GetCandleFhirVersion
|
||||||
|
echo.
|
||||||
|
echo Select the FHIR version for the Candle server:
|
||||||
|
echo 1. R4 (4.0)
|
||||||
|
echo 2. R4B (4.3)
|
||||||
|
echo 3. R5 (5.0)
|
||||||
|
CHOICE /C 123 /N /M "Enter your choice (1, 2, or 3):"
|
||||||
|
IF ERRORLEVEL 3 (
|
||||||
|
SET CANDLE_FHIR_VERSION=r5
|
||||||
|
goto :ModeSet
|
||||||
|
)
|
||||||
|
IF ERRORLEVEL 2 (
|
||||||
|
SET CANDLE_FHIR_VERSION=r4b
|
||||||
|
goto :ModeSet
|
||||||
|
)
|
||||||
|
IF ERRORLEVEL 1 (
|
||||||
|
SET CANDLE_FHIR_VERSION=r4
|
||||||
|
goto :ModeSet
|
||||||
|
)
|
||||||
|
echo Invalid input. Please try again.
|
||||||
|
goto :GetCandleFhirVersion
|
||||||
|
|
||||||
:ModeSet
|
:ModeSet
|
||||||
IF "%APP_MODE%"=="" (
|
IF "%APP_MODE%"=="" (
|
||||||
echo Invalid choice detected after checks. Exiting.
|
echo Invalid choice detected after checks. Exiting.
|
||||||
goto :eof
|
goto :eof
|
||||||
)
|
)
|
||||||
echo Selected Mode: %APP_MODE%
|
echo Selected Mode: %APP_MODE%
|
||||||
|
echo Server Type: %SERVER_TYPE%
|
||||||
echo.
|
echo.
|
||||||
REM === END CORRECTION ===
|
REM === END MODIFICATION ===
|
||||||
|
|
||||||
|
|
||||||
REM === Conditionally Execute HAPI Setup ===
|
REM === Conditionally Execute Server Setup ===
|
||||||
IF "%APP_MODE%"=="standalone" (
|
IF "%SERVER_TYPE%"=="hapi" (
|
||||||
echo Running Standalone setup including HAPI FHIR...
|
echo Running Hapi server setup...
|
||||||
echo.
|
echo.
|
||||||
|
|
||||||
REM --- Step 0: Clean up previous clone (optional) ---
|
REM --- Step 0: Clean up previous clone (optional) ---
|
||||||
echo Checking for existing directory: %CLONE_DIR%
|
echo Checking for existing directory: %CLONE_DIR_HAPI%
|
||||||
if exist "%CLONE_DIR%" (
|
if exist "%CLONE_DIR_HAPI%" (
|
||||||
echo Found existing directory, removing it...
|
echo Found existing directory, removing it...
|
||||||
rmdir /s /q "%CLONE_DIR%"
|
rmdir /s /q "%CLONE_DIR_HAPI%"
|
||||||
if errorlevel 1 (
|
if errorlevel 1 (
|
||||||
echo ERROR: Failed to remove existing directory: %CLONE_DIR%
|
echo ERROR: Failed to remove existing directory: %CLONE_DIR_HAPI%
|
||||||
goto :error
|
goto :error
|
||||||
)
|
)
|
||||||
echo Existing directory removed.
|
echo Existing directory removed.
|
||||||
@ -62,8 +135,8 @@ IF "%APP_MODE%"=="standalone" (
|
|||||||
echo.
|
echo.
|
||||||
|
|
||||||
REM --- Step 1: Clone the HAPI FHIR server repository ---
|
REM --- Step 1: Clone the HAPI FHIR server repository ---
|
||||||
echo Cloning repository: %REPO_URL% into %CLONE_DIR%...
|
echo Cloning repository: %REPO_URL_HAPI% into %CLONE_DIR_HAPI%...
|
||||||
git clone "%REPO_URL%" "%CLONE_DIR%"
|
git clone "%REPO_URL_HAPI%" "%CLONE_DIR_HAPI%"
|
||||||
if errorlevel 1 (
|
if errorlevel 1 (
|
||||||
echo ERROR: Failed to clone repository. Check Git installation and network connection.
|
echo ERROR: Failed to clone repository. Check Git installation and network connection.
|
||||||
goto :error
|
goto :error
|
||||||
@ -72,10 +145,10 @@ IF "%APP_MODE%"=="standalone" (
|
|||||||
echo.
|
echo.
|
||||||
|
|
||||||
REM --- Step 2: Navigate into the cloned directory ---
|
REM --- Step 2: Navigate into the cloned directory ---
|
||||||
echo Changing directory to %CLONE_DIR%...
|
echo Changing directory to %CLONE_DIR_HAPI%...
|
||||||
cd "%CLONE_DIR%"
|
cd "%CLONE_DIR_HAPI%"
|
||||||
if errorlevel 1 (
|
if errorlevel 1 (
|
||||||
echo ERROR: Failed to change directory to %CLONE_DIR%.
|
echo ERROR: Failed to change directory to %CLONE_DIR_HAPI%.
|
||||||
goto :error
|
goto :error
|
||||||
)
|
)
|
||||||
echo Current directory: %CD%
|
echo Current directory: %CD%
|
||||||
@ -92,7 +165,7 @@ IF "%APP_MODE%"=="standalone" (
|
|||||||
)
|
)
|
||||||
echo Maven build completed successfully. ErrorLevel: %errorlevel%
|
echo Maven build completed successfully. ErrorLevel: %errorlevel%
|
||||||
echo.
|
echo.
|
||||||
|
|
||||||
REM --- Step 4: Copy the configuration file ---
|
REM --- Step 4: Copy the configuration file ---
|
||||||
echo ===> "Starting file copy (Step 4)..."
|
echo ===> "Starting file copy (Step 4)..."
|
||||||
echo Copying configuration file...
|
echo Copying configuration file...
|
||||||
@ -118,45 +191,141 @@ IF "%APP_MODE%"=="standalone" (
|
|||||||
echo Current directory: %CD%
|
echo Current directory: %CD%
|
||||||
echo.
|
echo.
|
||||||
|
|
||||||
) ELSE (
|
) ELSE IF "%SERVER_TYPE%"=="candle" (
|
||||||
echo Running Lite setup, skipping HAPI FHIR build...
|
echo Running FHIR Candle server setup...
|
||||||
REM Ensure the hapi-fhir-jpaserver directory doesn't exist or is empty if Lite mode is chosen after a standalone attempt
|
echo.
|
||||||
if exist "%CLONE_DIR%" (
|
|
||||||
echo Found existing HAPI directory in Lite mode. Removing it to avoid build issues...
|
REM --- Step 0: Clean up previous clone (optional) ---
|
||||||
rmdir /s /q "%CLONE_DIR%"
|
echo Checking for existing directory: %CLONE_DIR_CANDLE%
|
||||||
|
if exist "%CLONE_DIR_CANDLE%" (
|
||||||
|
echo Found existing directory, removing it...
|
||||||
|
rmdir /s /q "%CLONE_DIR_CANDLE%"
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo ERROR: Failed to remove existing directory: %CLONE_DIR_CANDLE%
|
||||||
|
goto :error
|
||||||
|
)
|
||||||
|
echo Existing directory removed.
|
||||||
|
) else (
|
||||||
|
echo Directory does not exist, proceeding with clone.
|
||||||
)
|
)
|
||||||
REM Create empty target directories expected by Dockerfile COPY, even if not used
|
echo.
|
||||||
mkdir "%CLONE_DIR%\target\classes" 2> nul
|
|
||||||
mkdir "%CLONE_DIR%\custom" 2> nul
|
REM --- Step 1: Clone the FHIR Candle server repository ---
|
||||||
REM Create a placeholder empty WAR file to satisfy Dockerfile COPY
|
echo Cloning repository: %REPO_URL_CANDLE% into %CLONE_DIR_CANDLE%...
|
||||||
echo. > "%CLONE_DIR%\target\ROOT.war"
|
git clone "%REPO_URL_CANDLE%" "%CLONE_DIR_CANDLE%"
|
||||||
echo. > "%CLONE_DIR%\target\classes\application.yaml"
|
if errorlevel 1 (
|
||||||
|
echo ERROR: Failed to clone repository. Check Git and Dotnet installation and network connection.
|
||||||
|
goto :error
|
||||||
|
)
|
||||||
|
echo Repository cloned successfully.
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM --- Step 2: Navigate into the cloned directory ---
|
||||||
|
echo Changing directory to %CLONE_DIR_CANDLE%...
|
||||||
|
cd "%CLONE_DIR_CANDLE%"
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo ERROR: Failed to change directory to %CLONE_DIR_CANDLE%.
|
||||||
|
goto :error
|
||||||
|
)
|
||||||
|
echo Current directory: %CD%
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM --- Step 3: Build the FHIR Candle server using Dotnet ---
|
||||||
|
echo ===> "Starting Dotnet build (Step 3)...""
|
||||||
|
dotnet publish -c Release -f net9.0 -o publish
|
||||||
|
echo ===> Dotnet command finished. Checking error level...
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo ERROR: Dotnet build failed. Check Dotnet SDK installation.
|
||||||
|
cd ..
|
||||||
|
goto :error
|
||||||
|
)
|
||||||
|
echo Dotnet build completed successfully. ErrorLevel: %errorlevel%
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM --- Step 4: Navigate back to the parent directory ---
|
||||||
|
echo ===> "Changing directory back (Step 4)..."
|
||||||
|
cd ..
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo ERROR: Failed to change back to the parent directory. ErrorLevel: %errorlevel%
|
||||||
|
goto :error
|
||||||
|
)
|
||||||
|
echo Current directory: %CD%
|
||||||
|
echo.
|
||||||
|
|
||||||
|
) ELSE (
|
||||||
|
echo Running Lite setup, skipping server build...
|
||||||
|
REM Ensure the server directories don't exist in Lite mode
|
||||||
|
if exist "%CLONE_DIR_HAPI%" (
|
||||||
|
echo Found existing HAPI directory in Lite mode. Removing it to avoid build issues...
|
||||||
|
rmdir /s /q "%CLONE_DIR_HAPI%"
|
||||||
|
)
|
||||||
|
if exist "%CLONE_DIR_CANDLE%" (
|
||||||
|
echo Found existing Candle directory in Lite mode. Removing it to avoid build issues...
|
||||||
|
rmdir /s /q "%CLONE_DIR_CANDLE%"
|
||||||
|
)
|
||||||
|
REM Create empty placeholder files to satisfy Dockerfile COPY commands in Lite mode.
|
||||||
|
mkdir "%CLONE_DIR_HAPI%\target\classes" 2> nul
|
||||||
|
mkdir "%CLONE_DIR_HAPI%\custom" 2> nul
|
||||||
|
echo. > "%CLONE_DIR_HAPI%\target\ROOT.war"
|
||||||
|
echo. > "%CLONE_DIR_HAPI%\target\classes\application.yaml"
|
||||||
|
mkdir "%CLONE_DIR_CANDLE%\publish" 2> nul
|
||||||
|
echo. > "%CLONE_DIR_CANDLE%\publish\fhir-candle.dll"
|
||||||
echo Placeholder files created for Lite mode build.
|
echo Placeholder files created for Lite mode build.
|
||||||
echo.
|
echo.
|
||||||
)
|
)
|
||||||
|
|
||||||
REM === Modify docker-compose.yml to set APP_MODE ===
|
REM === MODIFIED: Update docker-compose.yml to set APP_MODE and HAPI_FHIR_URL ===
|
||||||
echo Updating docker-compose.yml with APP_MODE=%APP_MODE%...
|
echo Updating docker-compose.yml with APP_MODE=%APP_MODE% and HAPI_FHIR_URL...
|
||||||
(
|
(
|
||||||
echo version: '3.8'
|
echo version: '3.8'
|
||||||
echo services:
|
echo services:
|
||||||
echo fhirflare:
|
echo fhirflare:
|
||||||
echo build:
|
echo build:
|
||||||
echo context: .
|
echo context: .
|
||||||
echo dockerfile: Dockerfile
|
IF "%SERVER_TYPE%"=="hapi" (
|
||||||
|
echo dockerfile: Dockerfile.hapi
|
||||||
|
) ELSE IF "%SERVER_TYPE%"=="candle" (
|
||||||
|
echo dockerfile: Dockerfile.candle
|
||||||
|
) ELSE (
|
||||||
|
echo dockerfile: Dockerfile.lite
|
||||||
|
)
|
||||||
echo ports:
|
echo ports:
|
||||||
echo - "5000:5000"
|
IF "%SERVER_TYPE%"=="candle" (
|
||||||
echo - "8080:8080" # Keep port exposed, even if Tomcat isn't running useful stuff in Lite
|
echo - "5000:5000"
|
||||||
|
echo - "5001:5826"
|
||||||
|
) ELSE (
|
||||||
|
echo - "5000:5000"
|
||||||
|
echo - "8080:8080"
|
||||||
|
)
|
||||||
echo volumes:
|
echo volumes:
|
||||||
echo - ./instance:/app/instance
|
echo - ./instance:/app/instance
|
||||||
echo - ./static/uploads:/app/static/uploads
|
echo - ./static/uploads:/app/static/uploads
|
||||||
echo - ./instance/hapi-h2-data/:/app/h2-data # Keep volume mounts consistent
|
IF "%SERVER_TYPE%"=="hapi" (
|
||||||
|
echo - ./instance/hapi-h2-data/:/app/h2-data # Keep volume mounts consistent
|
||||||
|
)
|
||||||
echo - ./logs:/app/logs
|
echo - ./logs:/app/logs
|
||||||
|
IF "%SERVER_TYPE%"=="hapi" (
|
||||||
|
echo - ./hapi-fhir-jpaserver/target/ROOT.war:/usr/local/tomcat/webapps/ROOT.war
|
||||||
|
echo - ./hapi-fhir-jpaserver/target/classes/application.yaml:/usr/local/tomcat/conf/application.yaml
|
||||||
|
) ELSE IF "%SERVER_TYPE%"=="candle" (
|
||||||
|
echo - ./fhir-candle/publish/:/app/fhir-candle-publish/
|
||||||
|
)
|
||||||
echo environment:
|
echo environment:
|
||||||
echo - FLASK_APP=app.py
|
echo - FLASK_APP=app.py
|
||||||
echo - FLASK_ENV=development
|
echo - FLASK_ENV=development
|
||||||
echo - NODE_PATH=/usr/lib/node_modules
|
echo - NODE_PATH=/usr/lib/node_modules
|
||||||
echo - APP_MODE=%APP_MODE%
|
echo - APP_MODE=%APP_MODE%
|
||||||
|
echo - APP_BASE_URL=http://localhost:5000
|
||||||
|
IF DEFINED CUSTOM_FHIR_URL_VAL (
|
||||||
|
echo - HAPI_FHIR_URL=!CUSTOM_FHIR_URL_VAL!
|
||||||
|
) ELSE (
|
||||||
|
IF "%SERVER_TYPE%"=="candle" (
|
||||||
|
echo - HAPI_FHIR_URL=http://localhost:5826/fhir/%CANDLE_FHIR_VERSION%
|
||||||
|
echo - ASPNETCORE_URLS=http://0.0.0.0:5826
|
||||||
|
) ELSE (
|
||||||
|
echo - HAPI_FHIR_URL=http://localhost:8080/fhir
|
||||||
|
)
|
||||||
|
)
|
||||||
echo command: supervisord -c /etc/supervisord.conf
|
echo command: supervisord -c /etc/supervisord.conf
|
||||||
) > docker-compose.yml.tmp
|
) > docker-compose.yml.tmp
|
||||||
|
|
||||||
@ -206,4 +375,4 @@ exit /b 1
|
|||||||
|
|
||||||
:eof
|
:eof
|
||||||
echo Script execution finished.
|
echo Script execution finished.
|
||||||
pause
|
pause
|
||||||
|
|||||||
129
Dockerfile
129
Dockerfile
@ -1,56 +1,73 @@
|
|||||||
# Base image with Python and Java
|
# Base image with Python and Java
|
||||||
FROM tomcat:10.1-jdk17
|
FROM tomcat:10.1-jdk17
|
||||||
|
|
||||||
# Install build dependencies, Node.js 18, and coreutils (for stdbuf)
|
# Install build dependencies, Node.js 18, and coreutils (for stdbuf)
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
python3 python3-pip python3-venv curl coreutils \
|
python3 python3-pip python3-venv curl coreutils git \
|
||||||
&& curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
|
&& curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
|
||||||
&& apt-get install -y --no-install-recommends nodejs \
|
&& apt-get install -y --no-install-recommends nodejs \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Install specific versions of GoFSH and SUSHI
|
# ADDED: Install the Dotnet SDK for the FHIR Candle server
|
||||||
# REMOVED pip install fhirpath from this line
|
# This makes the image a universal base for either server type
|
||||||
RUN npm install -g gofsh fsh-sushi
|
RUN apt-get update && apt-get install -y dotnet-sdk-6.0
|
||||||
|
|
||||||
# Set up Python environment
|
# Install specific versions of GoFSH and SUSHI
|
||||||
WORKDIR /app
|
RUN npm install -g gofsh fsh-sushi
|
||||||
RUN python3 -m venv /app/venv
|
|
||||||
ENV PATH="/app/venv/bin:$PATH"
|
# ADDED: Download the latest HL7 FHIR Validator CLI
|
||||||
|
RUN mkdir -p /app/validator_cli
|
||||||
# ADDED: Uninstall old fhirpath just in case it's in requirements.txt
|
WORKDIR /app/validator_cli
|
||||||
RUN pip uninstall -y fhirpath || true
|
# Download the validator JAR and a separate checksum file for verification
|
||||||
# ADDED: Install the new fhirpathpy library
|
RUN curl -L -o validator_cli.jar "https://github.com/hapifhir/org.hl7.fhir.core/releases/latest/download/validator_cli.jar"
|
||||||
RUN pip install --no-cache-dir fhirpathpy
|
|
||||||
|
# Set permissions for the downloaded file
|
||||||
# Copy Flask files
|
RUN chmod 755 validator_cli.jar
|
||||||
COPY requirements.txt .
|
|
||||||
# Install requirements (including Pydantic - check version compatibility if needed)
|
# Change back to the main app directory for the next steps
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
WORKDIR /app
|
||||||
COPY app.py .
|
# Set up Python environment
|
||||||
COPY services.py .
|
RUN python3 -m venv /app/venv
|
||||||
COPY forms.py .
|
ENV PATH="/app/venv/bin:$PATH"
|
||||||
COPY templates/ templates/
|
|
||||||
COPY static/ static/
|
# ADDED: Uninstall old fhirpath just in case it's in requirements.txt
|
||||||
COPY tests/ tests/
|
RUN pip uninstall -y fhirpath || true
|
||||||
|
# ADDED: Install the new fhirpathpy library
|
||||||
# Ensure /tmp, /app/h2-data, /app/static/uploads, and /app/logs are writable
|
RUN pip install --no-cache-dir fhirpathpy
|
||||||
RUN mkdir -p /tmp /app/h2-data /app/static/uploads /app/logs && chmod 777 /tmp /app/h2-data /app/static/uploads /app/logs
|
|
||||||
|
# Copy Flask files
|
||||||
# Copy pre-built HAPI WAR and configuration
|
COPY requirements.txt .
|
||||||
COPY hapi-fhir-jpaserver/target/ROOT.war /usr/local/tomcat/webapps/
|
# Install requirements (including Pydantic - check version compatibility if needed)
|
||||||
COPY hapi-fhir-jpaserver/target/classes/application.yaml /usr/local/tomcat/conf/
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
COPY hapi-fhir-jpaserver/target/classes/application.yaml /app/config/application.yaml
|
COPY app.py .
|
||||||
COPY hapi-fhir-jpaserver/target/classes/application.yaml /usr/local/tomcat/webapps/app/config/application.yaml
|
COPY services.py .
|
||||||
COPY hapi-fhir-jpaserver/custom/ /usr/local/tomcat/webapps/custom/
|
COPY forms.py .
|
||||||
|
COPY package.py .
|
||||||
# Install supervisord
|
COPY templates/ templates/
|
||||||
RUN pip install supervisor
|
COPY static/ static/
|
||||||
|
COPY tests/ tests/
|
||||||
# Configure supervisord
|
|
||||||
COPY supervisord.conf /etc/supervisord.conf
|
# Ensure /tmp, /app/h2-data, /app/static/uploads, and /app/logs are writable
|
||||||
|
RUN mkdir -p /tmp /app/h2-data /app/static/uploads /app/logs && chmod 777 /tmp /app/h2-data /app/static/uploads /app/logs
|
||||||
# Expose ports
|
|
||||||
EXPOSE 5000 8080
|
# Copy pre-built HAPI WAR and configuration
|
||||||
|
COPY hapi-fhir-jpaserver/target/ROOT.war /usr/local/tomcat/webapps/
|
||||||
# Start supervisord
|
COPY hapi-fhir-jpaserver/target/classes/application.yaml /usr/local/tomcat/conf/
|
||||||
CMD ["supervisord", "-c", "/etc/supervisord.conf"]
|
COPY hapi-fhir-jpaserver/target/classes/application.yaml /app/config/application.yaml
|
||||||
|
COPY hapi-fhir-jpaserver/target/classes/application.yaml /usr/local/tomcat/webapps/app/config/application.yaml
|
||||||
|
COPY hapi-fhir-jpaserver/custom/ /usr/local/tomcat/webapps/custom/
|
||||||
|
|
||||||
|
# ADDED: Copy pre-built Candle DLL files
|
||||||
|
COPY fhir-candle/publish/ /app/fhir-candle-publish/
|
||||||
|
|
||||||
|
# Install supervisord
|
||||||
|
RUN pip install supervisor
|
||||||
|
|
||||||
|
# Configure supervisord
|
||||||
|
COPY supervisord.conf /etc/supervisord.conf
|
||||||
|
|
||||||
|
# Expose ports
|
||||||
|
EXPOSE 5000 8080 5001
|
||||||
|
|
||||||
|
# Start supervisord
|
||||||
|
CMD ["supervisord", "-c", "/etc/supervisord.conf"]
|
||||||
|
|||||||
64
Dockerfile.candle
Normal file
64
Dockerfile.candle
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# Base image with Python and Dotnet
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:9.0
|
||||||
|
|
||||||
|
# Install build dependencies, Node.js 18, and coreutils (for stdbuf)
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
python3 python3-pip python3-venv \
|
||||||
|
default-jre-headless \
|
||||||
|
curl coreutils git \
|
||||||
|
&& curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
|
||||||
|
&& apt-get install -y --no-install-recommends nodejs \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install specific versions of GoFSH and SUSHI
|
||||||
|
RUN npm install -g gofsh fsh-sushi
|
||||||
|
|
||||||
|
# ADDED: Download the latest HL7 FHIR Validator CLI
|
||||||
|
RUN mkdir -p /app/validator_cli
|
||||||
|
WORKDIR /app/validator_cli
|
||||||
|
# Download the validator JAR and a separate checksum file for verification
|
||||||
|
RUN curl -L -o validator_cli.jar "https://github.com/hapifhir/org.hl7.fhir.core/releases/latest/download/validator_cli.jar"
|
||||||
|
|
||||||
|
# Set permissions for the downloaded file
|
||||||
|
RUN chmod 755 validator_cli.jar
|
||||||
|
|
||||||
|
# Change back to the main app directory for the next steps
|
||||||
|
WORKDIR /app
|
||||||
|
# Set up Python environment
|
||||||
|
RUN python3 -m venv /app/venv
|
||||||
|
ENV PATH="/app/venv/bin:$PATH"
|
||||||
|
|
||||||
|
# ADDED: Uninstall old fhirpath just in case it's in requirements.txt
|
||||||
|
RUN pip uninstall -y fhirpath || true
|
||||||
|
# ADDED: Install the new fhirpathpy library
|
||||||
|
RUN pip install --no-cache-dir fhirpathpy
|
||||||
|
|
||||||
|
# Copy Flask files
|
||||||
|
COPY requirements.txt .
|
||||||
|
# Install requirements (including Pydantic - check version compatibility if needed)
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
COPY app.py .
|
||||||
|
COPY services.py .
|
||||||
|
COPY forms.py .
|
||||||
|
COPY package.py .
|
||||||
|
COPY templates/ templates/
|
||||||
|
COPY static/ static/
|
||||||
|
COPY tests/ tests/
|
||||||
|
|
||||||
|
# Ensure /tmp, /app/h2-data, /app/static/uploads, and /app/logs are writable
|
||||||
|
RUN mkdir -p /tmp /app/h2-data /app/static/uploads /app/logs && chmod 777 /tmp /app/h2-data /app/static/uploads /app/logs
|
||||||
|
|
||||||
|
# Copy pre-built Candle DLL files
|
||||||
|
COPY fhir-candle/publish/ /app/fhir-candle-publish/
|
||||||
|
|
||||||
|
# Install supervisord
|
||||||
|
RUN pip install supervisor
|
||||||
|
|
||||||
|
# Configure supervisord
|
||||||
|
COPY supervisord.conf /etc/supervisord.conf
|
||||||
|
|
||||||
|
# Expose ports
|
||||||
|
EXPOSE 5000 5001
|
||||||
|
|
||||||
|
# Start supervisord
|
||||||
|
CMD ["supervisord", "-c", "/etc/supervisord.conf"]
|
||||||
66
Dockerfile.hapi
Normal file
66
Dockerfile.hapi
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
# Base image with Python, Java, and Maven
|
||||||
|
FROM tomcat:10.1-jdk17
|
||||||
|
|
||||||
|
# Install build dependencies, Node.js 18, and coreutils (for stdbuf)
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
python3 python3-pip python3-venv curl coreutils git maven \
|
||||||
|
&& curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
|
||||||
|
&& apt-get install -y --no-install-recommends nodejs \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install specific versions of GoFSH and SUSHI
|
||||||
|
RUN npm install -g gofsh fsh-sushi
|
||||||
|
|
||||||
|
# ADDED: Download the latest HL7 FHIR Validator CLI
|
||||||
|
RUN mkdir -p /app/validator_cli
|
||||||
|
WORKDIR /app/validator_cli
|
||||||
|
# Download the validator JAR and a separate checksum file for verification
|
||||||
|
RUN curl -L -o validator_cli.jar "https://github.com/hapifhir/org.hl7.fhir.core/releases/latest/download/validator_cli.jar"
|
||||||
|
|
||||||
|
# Set permissions for the downloaded file
|
||||||
|
RUN chmod 755 validator_cli.jar
|
||||||
|
|
||||||
|
# Change back to the main app directory for the next steps
|
||||||
|
WORKDIR /app
|
||||||
|
# Set up Python environment
|
||||||
|
RUN python3 -m venv /app/venv
|
||||||
|
ENV PATH="/app/venv/bin:$PATH"
|
||||||
|
|
||||||
|
# ADDED: Uninstall old fhirpath just in case it's in requirements.txt
|
||||||
|
RUN pip uninstall -y fhirpath || true
|
||||||
|
# ADDED: Install the new fhirpathpy library
|
||||||
|
RUN pip install --no-cache-dir fhirpathpy
|
||||||
|
|
||||||
|
# Copy Flask files
|
||||||
|
COPY requirements.txt .
|
||||||
|
# Install requirements (including Pydantic - check version compatibility if needed)
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
COPY app.py .
|
||||||
|
COPY services.py .
|
||||||
|
COPY forms.py .
|
||||||
|
COPY package.py .
|
||||||
|
COPY templates/ templates/
|
||||||
|
COPY static/ static/
|
||||||
|
COPY tests/ tests/
|
||||||
|
|
||||||
|
# Ensure /tmp, /app/h2-data, /app/static/uploads, and /app/logs are writable
|
||||||
|
RUN mkdir -p /tmp /app/h2-data /app/static/uploads /app/logs && chmod 777 /tmp /app/h2-data /app/static/uploads /app/logs
|
||||||
|
|
||||||
|
# Copy pre-built HAPI WAR and configuration
|
||||||
|
COPY hapi-fhir-jpaserver/target/ROOT.war /usr/local/tomcat/webapps/
|
||||||
|
COPY hapi-fhir-jpaserver/target/classes/application.yaml /usr/local/tomcat/conf/
|
||||||
|
COPY hapi-fhir-jpaserver/target/classes/application.yaml /app/config/application.yaml
|
||||||
|
COPY hapi-fhir-jpaserver/target/classes/application.yaml /usr/local/tomcat/webapps/app/config/application.yaml
|
||||||
|
COPY hapi-fhir-jpaserver/custom/ /usr/local/tomcat/webapps/custom/
|
||||||
|
|
||||||
|
# Install supervisord
|
||||||
|
RUN pip install supervisor
|
||||||
|
|
||||||
|
# Configure supervisord
|
||||||
|
COPY supervisord.conf /etc/supervisord.conf
|
||||||
|
|
||||||
|
# Expose ports
|
||||||
|
EXPOSE 5000 8080
|
||||||
|
|
||||||
|
# Start supervisord
|
||||||
|
CMD ["supervisord", "-c", "/etc/supervisord.conf"]
|
||||||
60
Dockerfile.lite
Normal file
60
Dockerfile.lite
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# Base image with Python and Node.js
|
||||||
|
FROM python:3.9-slim
|
||||||
|
|
||||||
|
# Install JRE and other dependencies for the validator
|
||||||
|
# We need to install `default-jre-headless` to get a minimal Java Runtime Environment
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
default-jre-headless \
|
||||||
|
curl coreutils git \
|
||||||
|
&& curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
|
||||||
|
&& apt-get install -y --no-install-recommends nodejs \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install specific versions of GoFSH and SUSHI
|
||||||
|
RUN npm install -g gofsh fsh-sushi
|
||||||
|
|
||||||
|
# ADDED: Download the latest HL7 FHIR Validator CLI
|
||||||
|
RUN mkdir -p /app/validator_cli
|
||||||
|
WORKDIR /app/validator_cli
|
||||||
|
# Download the validator JAR and a separate checksum file for verification
|
||||||
|
RUN curl -L -o validator_cli.jar "https://github.com/hapifhir/org.hl7.fhir.core/releases/latest/download/validator_cli.jar"
|
||||||
|
|
||||||
|
# Set permissions for the downloaded file
|
||||||
|
RUN chmod 755 validator_cli.jar
|
||||||
|
|
||||||
|
# Change back to the main app directory for the next steps
|
||||||
|
WORKDIR /app
|
||||||
|
# Set up Python environment
|
||||||
|
RUN python3 -m venv /app/venv
|
||||||
|
ENV PATH="/app/venv/bin:$PATH"
|
||||||
|
|
||||||
|
# ADDED: Uninstall old fhirpath just in case it's in requirements.txt
|
||||||
|
RUN pip uninstall -y fhirpath || true
|
||||||
|
# ADDED: Install the new fhirpathpy library
|
||||||
|
RUN pip install --no-cache-dir fhirpathpy
|
||||||
|
|
||||||
|
# Copy Flask files
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
COPY app.py .
|
||||||
|
COPY services.py .
|
||||||
|
COPY forms.py .
|
||||||
|
COPY package.py .
|
||||||
|
COPY templates/ templates/
|
||||||
|
COPY static/ static/
|
||||||
|
COPY tests/ tests/
|
||||||
|
|
||||||
|
# Ensure necessary directories are writable
|
||||||
|
RUN mkdir -p /app/static/uploads /app/logs && chmod 777 /app/static/uploads /app/logs
|
||||||
|
|
||||||
|
# Install supervisord
|
||||||
|
RUN pip install supervisor
|
||||||
|
|
||||||
|
# Configure supervisord
|
||||||
|
COPY supervisord.conf /etc/supervisord.conf
|
||||||
|
|
||||||
|
# Expose ports
|
||||||
|
EXPOSE 5000
|
||||||
|
|
||||||
|
# Start supervisord
|
||||||
|
CMD ["supervisord", "-c", "/etc/supervisord.conf"]
|
||||||
670
README.md
670
README.md
@ -1,25 +1,69 @@
|
|||||||
# FHIRFLARE IG Toolkit
|
# FHIRFLARE IG Toolkit
|
||||||
|

|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
The FHIRFLARE IG Toolkit is a Flask-based web application designed to streamline the management, processing, validation, and deployment of FHIR Implementation Guides (IGs). It offers a user-friendly interface for importing IG packages, extracting metadata, validating FHIR resources or bundles, pushing IGs to FHIR servers, and converting FHIR resources to FHIR Shorthand (FSH) using GoFSH with advanced features. The toolkit includes a live console for real-time feedback and a waiting spinner for FSH conversion, making it an essential tool for FHIR developers and implementers.
|
The FHIRFLARE IG Toolkit is a Flask-based web application designed to streamline the management, processing, validation, and deployment of FHIR Implementation Guides (IGs) and test data. It offers a user-friendly interface for importing IG packages, extracting metadata, validating FHIR resources or bundles, pushing IGs to FHIR servers, converting FHIR resources to FHIR Shorthand (FSH), uploading complex test data sets with dependency management, and retrieving/splitting FHIR bundles. The toolkit includes live consoles for real-time feedback, making it an essential tool for FHIR developers and implementers.
|
||||||
|
|
||||||
The application can run in two modes:
|
The application can run in four modes:
|
||||||
|
|
||||||
* **Standalone:** Includes a Dockerized Flask frontend, SQLite database, and an embedded HAPI FHIR server for local validation and interaction.
|
|
||||||
* **Lite:** Includes only the Dockerized Flask frontend and SQLite database, excluding the local HAPI FHIR server. Requires connection to external FHIR servers for certain features.
|
* Installation Modes (Lite, Custom URL, Hapi, and Candle)
|
||||||
|
This toolkit now offers four primary installation modes, configured via a simple command-line prompt during setup:
|
||||||
|
|
||||||
|
Lite Mode
|
||||||
|
|
||||||
|
Includes the Flask frontend, SQLite database, and core tooling (GoFSH, SUSHI) without an embedded FHIR server.
|
||||||
|
|
||||||
|
Requires you to provide a URL for an external FHIR server when using features like the FHIR API Explorer and validation.
|
||||||
|
|
||||||
|
Does not require Git, Maven, or .NET for setup.
|
||||||
|
|
||||||
|
Ideal for users who will always connect to an existing external FHIR server.
|
||||||
|
|
||||||
|
Custom URL Mode
|
||||||
|
|
||||||
|
Similar to Lite Mode, but prompts you for a specific external FHIR server URL to use as the default for the toolkit.
|
||||||
|
|
||||||
|
The application will be pre-configured to use your custom URL for all FHIR-related operations.
|
||||||
|
|
||||||
|
Does not require Git, Maven, or .NET for setup.
|
||||||
|
|
||||||
|
Hapi Mode
|
||||||
|
|
||||||
|
Includes the full FHIRFLARE toolkit and an embedded HAPI FHIR server.
|
||||||
|
|
||||||
|
The docker-compose configuration will proxy requests to the internal HAPI server, which is accessible via http://localhost:8080/fhir.
|
||||||
|
|
||||||
|
Requires Git and Maven during the initial build process to compile the HAPI FHIR server.
|
||||||
|
|
||||||
|
Ideal for users who want a self-contained, offline development and testing environment.
|
||||||
|
|
||||||
|
Candle Mode
|
||||||
|
|
||||||
|
Includes the full FHIRFLARE toolkit and an embedded FHIR Candle server.
|
||||||
|
|
||||||
|
The docker-compose configuration will proxy requests to the internal Candle server, which is accessible via http://localhost:5001/fhir/<version>.
|
||||||
|
|
||||||
|
Requires Git and the .NET SDK during the initial build process.
|
||||||
|
|
||||||
|
Ideal for users who want to use the .NET-based FHIR server for development and testing.
|
||||||
|
|
||||||
## Installation Modes (Lite vs. Standalone)
|
## Installation Modes (Lite vs. Standalone)
|
||||||
|
|
||||||
This toolkit offers two primary installation modes to suit different needs:
|
This toolkit offers two primary installation modes to suit different needs:
|
||||||
|
|
||||||
* **Standalone Version:**
|
* **Standalone Version - Hapi / Candle:**
|
||||||
* Includes the full FHIRFLARE Toolkit application **and** an embedded HAPI FHIR server running locally within the Docker environment.
|
* Includes the full FHIRFLARE Toolkit application **and** an embedded HAPI FHIR server running locally within the Docker environment.
|
||||||
* Allows for local FHIR resource validation using HAPI FHIR's capabilities.
|
* Allows for local FHIR resource validation using HAPI FHIR's capabilities.
|
||||||
* Enables the "Use Local HAPI" option in the FHIR API Explorer and FHIR UI Operations pages, proxying requests to the internal HAPI server (`http://localhost:8080/fhir`).
|
* Enables the "Use Local HAPI" option in the FHIR API Explorer and FHIR UI Operations pages, proxying requests to the internal HAPI server (`http://localhost:8080/fhir`).
|
||||||
* Requires Git and Maven during the initial build process (via the `.bat` script or manual steps) to prepare the HAPI FHIR server.
|
* Requires Git and Maven during the initial build process (via the `.bat` script or manual steps) to prepare the HAPI FHIR server.
|
||||||
* Ideal for users who want a self-contained environment for development and testing or who don't have readily available external FHIR servers.
|
* Ideal for users who want a self-contained environment for development and testing or who don't have readily available external FHIR servers.
|
||||||
|
|
||||||
|
* **Standalone Version - Custom Mode:**
|
||||||
|
* Includes the full FHIRFLARE Toolkit application **and** point the Environmet variable at your chosen custom fhir url endpoint allowing for Custom setups
|
||||||
|
|
||||||
|
|
||||||
* **Lite Version:**
|
* **Lite Version:**
|
||||||
* Includes the FHIRFLARE Toolkit application **without** the embedded HAPI FHIR server.
|
* Includes the FHIRFLARE Toolkit application **without** the embedded HAPI FHIR server.
|
||||||
* Requires users to provide URLs for external FHIR servers when using features like the FHIR API Explorer and FHIR UI Operations pages. The "Use Local HAPI" option will be disabled in the UI.
|
* Requires users to provide URLs for external FHIR servers when using features like the FHIR API Explorer and FHIR UI Operations pages. The "Use Local HAPI" option will be disabled in the UI.
|
||||||
@ -29,17 +73,52 @@ This toolkit offers two primary installation modes to suit different needs:
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* **Import IGs:** Download FHIR IG packages and dependencies from a package registry, supporting flexible version formats (e.g., `1.2.3`, `1.1.0-preview`, `1.1.2-ballot`, `current`).
|
* **Import IGs:** Download FHIR IG packages and dependencies from a package registry, supporting flexible version formats (e.g., `1.2.3`, `1.1.0-preview`, `current`) and dependency pulling modes (Recursive, Patch Canonical, Tree Shaking).
|
||||||
|
* **Enhanced Package Search and Import:**
|
||||||
|
* Interactive page (`/search-and-import`) to search for FHIR IG packages from configured registries.
|
||||||
|
* Displays package details, version history, dependencies, and dependents.
|
||||||
|
* Utilizes a local database cache (`CachedPackage`) for faster subsequent searches.
|
||||||
|
* Background task to refresh the package cache from registries (`/api/refresh-cache-task`).
|
||||||
|
* Direct import from search results.
|
||||||
* **Manage IGs:** View, process, unload, or delete downloaded IGs, with duplicate detection and resolution.
|
* **Manage IGs:** View, process, unload, or delete downloaded IGs, with duplicate detection and resolution.
|
||||||
* **Process IGs:** Extract resource types, profiles, must-support elements, examples, and profile relationships (`structuredefinition-compliesWithProfile` and `structuredefinition-imposeProfile`).
|
* **Process IGs:** Extract resource types, profiles, must-support elements, examples, and profile relationships (`structuredefinition-compliesWithProfile` and `structuredefinition-imposeProfile`).
|
||||||
* **Validate FHIR Resources/Bundles:** Validate single FHIR resources or bundles against selected IGs, with detailed error and warning reports (alpha feature, work in progress). *Note: Lite version uses local SD checks only.*
|
* **Validate FHIR Resources/Bundles:** Validate single FHIR resources or bundles against selected IGs, with detailed error and warning reports (alpha feature). *Note: Lite version uses local SD checks only.*
|
||||||
* **Push IGs:** Upload IG resources to a target FHIR server with real-time console output and optional validation against imposed profiles.
|
* **Push IGs:** Upload IG resources (and optionally dependencies) to a target FHIR server. Features include:
|
||||||
* **Profile Relationships:** Display and validate `compliesWithProfile` and `imposeProfile` extensions in the UI.
|
* Real-time console output.
|
||||||
|
* Authentication support (Bearer Token).
|
||||||
|
* Filtering by resource type or specific files to skip.
|
||||||
|
* Semantic comparison to skip uploading identical resources (override with **Force Upload** option).
|
||||||
|
* Correct handling of canonical resources (searching by URL/version before deciding POST/PUT).
|
||||||
|
* Dry run mode for simulation.
|
||||||
|
* Verbose logging option.
|
||||||
|
* **Upload Test Data:** Upload complex sets of test data (individual JSON/XML files or ZIP archives) to a target FHIR server. Features include:
|
||||||
|
* Robust parsing of JSON and XML (using `fhir.resources` library when available).
|
||||||
|
* Automatic dependency analysis based on resource references within the uploaded set.
|
||||||
|
* Topological sorting to ensure resources are uploaded in the correct order.
|
||||||
|
* Cycle detection in dependencies.
|
||||||
|
* Choice of individual resource uploads or a single transaction bundle.
|
||||||
|
* **Optional Pre-Upload Validation:** Validate resources against a selected profile package before uploading.
|
||||||
|
* **Optional Conditional Uploads (Individual Mode):** Check resource existence (GET) and use conditional `If-Match` headers for updates (PUT) or create resources (PUT/POST). Falls back to simple PUT if unchecked.
|
||||||
|
* Configurable error handling (stop on first error or continue).
|
||||||
|
* Authentication support (Bearer Token).
|
||||||
|
* Streaming progress log via the UI.
|
||||||
|
* Handles large numbers of files using a custom form parser.
|
||||||
|
* **Profile Relationships:** Display and validate `compliesWithProfile` and `imposeProfile` extensions in the UI (configurable).
|
||||||
* **FSH Converter:** Convert FHIR JSON/XML resources to FHIR Shorthand (FSH) using GoFSH, with advanced options (Package context, Output styles, Log levels, FHIR versions, Fishing Trip, Dependencies, Indentation, Meta Profile handling, Alias File, No Alias). Includes a waiting spinner.
|
* **FSH Converter:** Convert FHIR JSON/XML resources to FHIR Shorthand (FSH) using GoFSH, with advanced options (Package context, Output styles, Log levels, FHIR versions, Fishing Trip, Dependencies, Indentation, Meta Profile handling, Alias File, No Alias). Includes a waiting spinner.
|
||||||
* **FHIR Interaction UIs:** Explore FHIR server capabilities and interact with resources using the "FHIR API Explorer" and "FHIR UI Operations" pages. *Note: Lite version requires custom server URLs.*
|
* **Retrieve and Split Bundles:**
|
||||||
* **API Support:** RESTful API endpoints for importing, pushing, and retrieving IG metadata.
|
* Retrieve specified resource types as bundles from a FHIR server.
|
||||||
* **Live Console:** Real-time logs for push, validation, and FSH conversion operations.
|
* Optionally fetch referenced resources, either individually or as full bundles for each referenced type.
|
||||||
* **Configurable Behavior:** Enable/disable imposed profile validation and UI display of profile relationships.
|
* Split uploaded ZIP files containing bundles into individual resource JSON files.
|
||||||
|
* Download retrieved/split resources as a ZIP archive.
|
||||||
|
* Streaming progress log via the UI for retrieval operations.
|
||||||
|
* **FHIR Interaction UIs:** Explore FHIR server capabilities and interact with resources using the "FHIR API Explorer" (simple GET/POST/PUT/DELETE) and "FHIR UI Operations" (Swagger-like interface based on CapabilityStatement). *Note: Lite version requires custom server URLs.*
|
||||||
|
* **HAPI FHIR Configuration (Standalone Mode):**
|
||||||
|
* A dedicated page (`/config-hapi`) to view and edit the `application.yaml` configuration for the embedded HAPI FHIR server.
|
||||||
|
* Allows modification of HAPI FHIR properties directly from the UI.
|
||||||
|
* Option to restart the HAPI FHIR server (Tomcat) to apply changes.
|
||||||
|
* **API Support:** RESTful API endpoints for importing, pushing, retrieving metadata, validating, uploading test data, and retrieving/splitting bundles.
|
||||||
|
* **Live Console:** Real-time logs for push, validation, upload test data, FSH conversion, and bundle retrieval operations.
|
||||||
|
* **Configurable Behavior:** Control validation modes, display options via `app.config`.
|
||||||
* **Theming:** Supports light and dark modes.
|
* **Theming:** Supports light and dark modes.
|
||||||
|
|
||||||
## Technology Stack
|
## Technology Stack
|
||||||
@ -50,7 +129,8 @@ This toolkit offers two primary installation modes to suit different needs:
|
|||||||
* Docker, Docker Compose, Supervisor
|
* Docker, Docker Compose, Supervisor
|
||||||
* Node.js 18+ (for GoFSH/SUSHI), GoFSH, SUSHI
|
* Node.js 18+ (for GoFSH/SUSHI), GoFSH, SUSHI
|
||||||
* HAPI FHIR (Standalone version only)
|
* HAPI FHIR (Standalone version only)
|
||||||
* Requests 2.31.0, Tarfile, Logging
|
* Requests 2.31.0, Tarfile, Logging, Werkzeug
|
||||||
|
* fhir.resources (optional, for robust XML parsing)
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
@ -66,6 +146,7 @@ This is the easiest way to get started without needing Git or Maven. Choose the
|
|||||||
|
|
||||||
**Lite Version (No local HAPI FHIR):**
|
**Lite Version (No local HAPI FHIR):**
|
||||||
|
|
||||||
|
```bash
|
||||||
# Pull the latest Lite image
|
# Pull the latest Lite image
|
||||||
docker pull ghcr.io/sudo-jhare/fhirflare-ig-toolkit-lite:latest
|
docker pull ghcr.io/sudo-jhare/fhirflare-ig-toolkit-lite:latest
|
||||||
|
|
||||||
@ -80,8 +161,9 @@ docker run -d \
|
|||||||
-v ./logs:/app/logs \
|
-v ./logs:/app/logs \
|
||||||
--name fhirflare-lite \
|
--name fhirflare-lite \
|
||||||
ghcr.io/sudo-jhare/fhirflare-ig-toolkit-lite:latest
|
ghcr.io/sudo-jhare/fhirflare-ig-toolkit-lite:latest
|
||||||
|
Standalone Version (Includes local HAPI FHIR):
|
||||||
|
|
||||||
|
Bash
|
||||||
|
|
||||||
# Pull the latest Standalone image
|
# Pull the latest Standalone image
|
||||||
docker pull ghcr.io/sudo-jhare/fhirflare-ig-toolkit-standalone:latest
|
docker pull ghcr.io/sudo-jhare/fhirflare-ig-toolkit-standalone:latest
|
||||||
@ -98,137 +180,193 @@ docker run -d \
|
|||||||
-v ./logs:/app/logs \
|
-v ./logs:/app/logs \
|
||||||
--name fhirflare-standalone \
|
--name fhirflare-standalone \
|
||||||
ghcr.io/sudo-jhare/fhirflare-ig-toolkit-standalone:latest
|
ghcr.io/sudo-jhare/fhirflare-ig-toolkit-standalone:latest
|
||||||
|
Building from Source (Developers)
|
||||||
|
Using Windows .bat Scripts (Standalone Version Only):
|
||||||
|
|
||||||
|
First Time Setup:
|
||||||
|
|
||||||
Run Build and Run for first time.bat:
|
Run Build and Run for first time.bat:
|
||||||
|
|
||||||
|
Code snippet
|
||||||
|
|
||||||
cd "<project folder>"
|
cd "<project folder>"
|
||||||
git clone https://github.com/hapifhir/hapi-fhir-jpaserver-starter.git hapi-fhir-jpaserver
|
git clone [https://github.com/hapifhir/hapi-fhir-jpaserver-starter.git](https://github.com/hapifhir/hapi-fhir-jpaserver-starter.git) hapi-fhir-jpaserver
|
||||||
copy .\hapi-fhir-Setup\target\classes\application.yaml .\hapi-fhir-jpaserver\target\classes\application.yaml
|
copy .\\hapi-fhir-Setup\\target\\classes\\application.yaml .\\hapi-fhir-jpaserver\\target\\classes\\application.yaml
|
||||||
mvn clean package -DskipTests=true -Pboot
|
mvn clean package -DskipTests=true -Pboot
|
||||||
docker-compose build --no-cache
|
docker-compose build --no-cache
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
|
|
||||||
This clones the HAPI FHIR server, copies configuration, builds the project, and starts the containers.
|
This clones the HAPI FHIR server, copies configuration, builds the project, and starts the containers.
|
||||||
|
|
||||||
|
|
||||||
Subsequent Runs:
|
Subsequent Runs:
|
||||||
|
|
||||||
Run Run.bat:
|
Run Run.bat:
|
||||||
|
|
||||||
|
Code snippet
|
||||||
|
|
||||||
cd "<project folder>"
|
cd "<project folder>"
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
|
|
||||||
This starts the Flask app (port 5000) and HAPI FHIR server (port 8080).
|
This starts the Flask app (port 5000) and HAPI FHIR server (port 8080).
|
||||||
|
|
||||||
|
|
||||||
Access the Application:
|
Access the Application:
|
||||||
|
|
||||||
Flask UI: http://localhost:5000
|
Flask UI: http://localhost:5000
|
||||||
HAPI FHIR server: http://localhost:8080
|
HAPI FHIR server: http://localhost:8080
|
||||||
|
Manual Setup (Linux/MacOS/Windows):
|
||||||
|
|
||||||
|
Preparation (Standalone Version Only):
|
||||||
|
|
||||||
|
Bash
|
||||||
|
|
||||||
Manual Setup (Linux/MacOS/Windows)
|
|
||||||
Preparation:
|
|
||||||
cd <project folder>
|
cd <project folder>
|
||||||
git clone https://github.com/hapifhir/hapi-fhir-jpaserver-starter.git hapi-fhir-jpaserver
|
git clone [https://github.com/hapifhir/hapi-fhir-jpaserver-starter.git](https://github.com/hapifhir/hapi-fhir-jpaserver-starter.git) hapi-fhir-jpaserver
|
||||||
cp ./hapi-fhir-Setup/target/classes/application.yaml ./hapi-fhir-jpaserver/target/classes/application.yaml
|
cp ./hapi-fhir-Setup/target/classes/application.yaml ./hapi-fhir-jpaserver/target/classes/application.yaml
|
||||||
|
|
||||||
Build:
|
Build:
|
||||||
|
|
||||||
|
Bash
|
||||||
|
|
||||||
|
# Build HAPI FHIR (Standalone Version Only)
|
||||||
mvn clean package -DskipTests=true -Pboot
|
mvn clean package -DskipTests=true -Pboot
|
||||||
|
|
||||||
|
# Build Docker Image (Specify APP_MODE=lite in docker-compose.yml for Lite version)
|
||||||
docker-compose build --no-cache
|
docker-compose build --no-cache
|
||||||
|
|
||||||
Run:
|
Run:
|
||||||
docker-compose up -d
|
|
||||||
|
|
||||||
|
Bash
|
||||||
|
|
||||||
|
docker-compose up -d
|
||||||
Access the Application:
|
Access the Application:
|
||||||
|
|
||||||
Flask UI: http://localhost:5000
|
Flask UI: http://localhost:5000
|
||||||
HAPI FHIR server: http://localhost:8080
|
HAPI FHIR server (Standalone only): http://localhost:8080
|
||||||
|
Local Development (Without Docker):
|
||||||
|
|
||||||
Local Development (Without Docker)
|
|
||||||
Clone the Repository:
|
Clone the Repository:
|
||||||
git clone https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit.git
|
|
||||||
cd FHIRFLARE-IG-Toolkit
|
|
||||||
|
|
||||||
|
Bash
|
||||||
|
|
||||||
|
git clone [https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit.git](https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit.git)
|
||||||
|
cd FHIRFLARE-IG-Toolkit
|
||||||
Install Dependencies:
|
Install Dependencies:
|
||||||
|
|
||||||
|
Bash
|
||||||
|
|
||||||
python -m venv venv
|
python -m venv venv
|
||||||
source venv/bin/activate # On Windows: venv\Scripts\activate
|
source venv/bin/activate # On Windows: venv\Scripts\activate
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
|
|
||||||
Install Node.js, GoFSH, and SUSHI (for FSH Converter):
|
Install Node.js, GoFSH, and SUSHI (for FSH Converter):
|
||||||
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo bash -
|
|
||||||
sudo apt-get install -y nodejs
|
|
||||||
npm install -g gofsh fsh-sushi
|
|
||||||
|
|
||||||
|
Bash
|
||||||
|
|
||||||
|
# Example for Debian/Ubuntu
|
||||||
|
curl -fsSL [https://deb.nodesource.com/setup_18.x](https://deb.nodesource.com/setup_18.x) | sudo bash -
|
||||||
|
sudo apt-get install -y nodejs
|
||||||
|
# Install globally
|
||||||
|
npm install -g gofsh fsh-sushi
|
||||||
Set Environment Variables:
|
Set Environment Variables:
|
||||||
|
|
||||||
|
Bash
|
||||||
|
|
||||||
export FLASK_SECRET_KEY='your-secure-secret-key'
|
export FLASK_SECRET_KEY='your-secure-secret-key'
|
||||||
export API_KEY='your-api-key'
|
export API_KEY='your-api-key'
|
||||||
|
# Optional: Set APP_MODE to 'lite' if desired
|
||||||
|
# export APP_MODE='lite'
|
||||||
Initialize Directories:
|
Initialize Directories:
|
||||||
mkdir -p instance static/uploads logs
|
|
||||||
chmod -R 777 instance static/uploads logs
|
|
||||||
|
|
||||||
|
Bash
|
||||||
|
|
||||||
|
mkdir -p instance static/uploads logs
|
||||||
|
# Ensure write permissions if needed
|
||||||
|
# chmod -R 777 instance static/uploads logs
|
||||||
Run the Application:
|
Run the Application:
|
||||||
|
|
||||||
|
Bash
|
||||||
|
|
||||||
export FLASK_APP=app.py
|
export FLASK_APP=app.py
|
||||||
flask run
|
flask run
|
||||||
|
|
||||||
Access at http://localhost:5000.
|
Access at http://localhost:5000.
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
Import an IG
|
Import an IG
|
||||||
|
### Search, View Details, and Import Packages
|
||||||
|
Navigate to **Search and Import Packages** (`/search-and-import`).
|
||||||
|
1. The page will load a list of available FHIR Implementation Guide packages from a local cache or by fetching from configured registries.
|
||||||
|
* A loading animation and progress messages are shown if fetching from registries.
|
||||||
|
* The timestamp of the last cache update is displayed.
|
||||||
|
2. Use the search bar to filter packages by name or author.
|
||||||
|
3. Packages are paginated for easier Browse.
|
||||||
|
4. For each package, you can:
|
||||||
|
* View its latest official and absolute versions.
|
||||||
|
* Click on the package name to navigate to a **detailed view** (`/package-details/<name>`) showing:
|
||||||
|
* Comprehensive metadata (author, FHIR version, canonical URL, description).
|
||||||
|
* A full list of available versions with publication dates.
|
||||||
|
* Declared dependencies.
|
||||||
|
* Other packages that depend on it (dependents).
|
||||||
|
* Version history (logs).
|
||||||
|
* Directly import a specific version using the "Import" button on the search page or the details page.
|
||||||
|
5. **Cache Management:**
|
||||||
|
* A "Clear & Refresh Cache" button is available to trigger a background task (`/api/refresh-cache-task`) that clears the local database and in-memory cache and fetches the latest package information from all configured registries. Progress is shown via a live log.
|
||||||
|
|
||||||
Navigate to Import IG (/import-ig).
|
|
||||||
Enter a package name (e.g., hl7.fhir.au.core) and version (e.g., 1.1.0-preview).
|
Enter a package name (e.g., hl7.fhir.au.core) and version (e.g., 1.1.0-preview).
|
||||||
Choose a dependency mode:
|
Choose a dependency mode:
|
||||||
Recursive: Import all dependencies.
|
Current Recursive: Import all dependencies listed in package.json recursively.
|
||||||
Patch Canonical: Import only canonical FHIR packages.
|
Patch Canonical Versions: Import only canonical FHIR packages (e.g., hl7.fhir.r4.core).
|
||||||
Tree Shaking: Import only used dependencies.
|
Tree Shaking: Import only dependencies containing resources actually used by the main package.
|
||||||
|
|
||||||
|
|
||||||
Click Import to download the package and dependencies.
|
Click Import to download the package and dependencies.
|
||||||
|
|
||||||
Manage IGs
|
Manage IGs
|
||||||
|
|
||||||
Go to Manage FHIR Packages (/view-igs) to view downloaded and processed IGs.
|
Go to Manage FHIR Packages (/view-igs) to view downloaded and processed IGs.
|
||||||
|
|
||||||
Actions:
|
Actions:
|
||||||
Process: Extract metadata (resource types, profiles, must-support elements, examples).
|
Process: Extract metadata (resource types, profiles, must-support elements, examples).
|
||||||
Unload: Remove processed IG data from the database.
|
Unload: Remove processed IG data from the database.
|
||||||
Delete: Remove package files from the filesystem.
|
Delete: Remove package files from the filesystem.
|
||||||
|
|
||||||
|
|
||||||
Duplicates are highlighted for resolution.
|
Duplicates are highlighted for resolution.
|
||||||
|
|
||||||
View Processed IGs
|
View Processed IGs
|
||||||
|
|
||||||
After processing, view IG details (/view-ig/<id>), including:
|
After processing, view IG details (/view-ig/<id>), including:
|
||||||
|
|
||||||
Resource types and profiles.
|
Resource types and profiles.
|
||||||
Must-support elements and examples.
|
Must-support elements and examples.
|
||||||
Profile relationships (compliesWithProfile, imposeProfile) if enabled (DISPLAY_PROFILE_RELATIONSHIPS).
|
Profile relationships (compliesWithProfile, imposeProfile) if enabled (DISPLAY_PROFILE_RELATIONSHIPS).
|
||||||
|
Interactive StructureDefinition viewer (Differential, Snapshot, Must Support, Key Elements, Constraints, Terminology, Search Params).
|
||||||
|
|
||||||
|
|
||||||
Validate FHIR Resources/Bundles
|
Validate FHIR Resources/Bundles
|
||||||
|
|
||||||
Navigate to Validate FHIR Sample (/validate-sample).
|
Navigate to Validate FHIR Sample (/validate-sample).
|
||||||
|
|
||||||
Select a package (e.g., hl7.fhir.au.core#1.1.0-preview).
|
Select a package (e.g., hl7.fhir.au.core#1.1.0-preview).
|
||||||
Choose Single Resource or Bundle mode.
|
Choose Single Resource or Bundle mode.
|
||||||
Paste or upload FHIR JSON/XML (e.g., a Patient resource).
|
Paste or upload FHIR JSON/XML (e.g., a Patient resource).
|
||||||
Submit to view validation errors/warnings.
|
Submit to view validation errors/warnings. Note: Alpha feature; report issues to GitHub (remove PHI).
|
||||||
Note: Alpha feature; report issues to GitHub (remove PHI).
|
|
||||||
|
|
||||||
Push IGs to a FHIR Server
|
Push IGs to a FHIR Server
|
||||||
|
|
||||||
Go to Push IGs (/push-igs).
|
Go to Push IGs (/push-igs).
|
||||||
Select a package, enter a FHIR server URL (e.g., http://localhost:8080/fhir), and choose whether to include dependencies.
|
|
||||||
Click Push to FHIR Server to upload resources, with validation against imposed profiles (if enabled via VALIDATE_IMPOSED_PROFILES).
|
Select a downloaded package.
|
||||||
|
Enter the Target FHIR Server URL.
|
||||||
|
Configure Authentication (None, Bearer Token).
|
||||||
|
Choose options: Include Dependencies, Force Upload (skips comparison check), Dry Run, Verbose Log.
|
||||||
|
Optionally filter by Resource Types (comma-separated) or Skip Specific Files (paths within package, comma/newline separated).
|
||||||
|
Click Push to FHIR Server to upload resources. Canonical resources are checked before upload. Identical resources are skipped unless Force Upload is checked.
|
||||||
Monitor progress in the live console.
|
Monitor progress in the live console.
|
||||||
|
Upload Test Data
|
||||||
|
Navigate to Upload Test Data (/upload-test-data).
|
||||||
|
|
||||||
|
Enter the Target FHIR Server URL.
|
||||||
|
Configure Authentication (None, Bearer Token).
|
||||||
|
Select one or more .json, .xml files, or a single .zip file containing test resources.
|
||||||
|
Optionally check Validate Resources Before Upload? and select a Validation Profile Package.
|
||||||
|
Choose Upload Mode:
|
||||||
|
Individual Resources: Uploads each resource one by one in dependency order.
|
||||||
|
Transaction Bundle: Uploads all resources in a single transaction.
|
||||||
|
Optionally check Use Conditional Upload (Individual Mode Only)? to use If-Match headers for updates.
|
||||||
|
Choose Error Handling:
|
||||||
|
Stop on First Error: Halts the process if any validation or upload fails.
|
||||||
|
Continue on Error: Reports errors but attempts to process/upload remaining resources.
|
||||||
|
Click Upload and Process. The tool parses files, optionally validates, analyzes dependencies, topologically sorts resources, and uploads them according to selected options.
|
||||||
|
Monitor progress in the streaming log output.
|
||||||
Convert FHIR to FSH
|
Convert FHIR to FSH
|
||||||
|
|
||||||
Navigate to FSH Converter (/fsh-converter).
|
Navigate to FSH Converter (/fsh-converter).
|
||||||
|
|
||||||
Optionally select a package for context (e.g., hl7.fhir.au.core#1.1.0-preview).
|
Optionally select a package for context (e.g., hl7.fhir.au.core#1.1.0-preview).
|
||||||
Choose input mode:
|
Choose input mode:
|
||||||
Upload File: Upload a FHIR JSON/XML file.
|
Upload File: Upload a FHIR JSON/XML file.
|
||||||
Paste Text: Paste FHIR JSON/XML content.
|
Paste Text: Paste FHIR JSON/XML content.
|
||||||
|
|
||||||
|
|
||||||
Configure options:
|
Configure options:
|
||||||
Output Style: file-per-definition, group-by-fsh-type, group-by-profile, single-file.
|
Output Style: file-per-definition, group-by-fsh-type, group-by-profile, single-file.
|
||||||
Log Level: error, warn, info, debug.
|
Log Level: error, warn, info, debug.
|
||||||
@ -239,239 +377,228 @@ Indent Rules: Enable context path indentation for readable FSH.
|
|||||||
Meta Profile: Choose only-one, first, or none for meta.profile handling.
|
Meta Profile: Choose only-one, first, or none for meta.profile handling.
|
||||||
Alias File: Upload an FSH file with aliases (e.g., $MyAlias = http://example.org).
|
Alias File: Upload an FSH file with aliases (e.g., $MyAlias = http://example.org).
|
||||||
No Alias: Disable automatic alias generation.
|
No Alias: Disable automatic alias generation.
|
||||||
|
|
||||||
|
|
||||||
Click Convert to FSH to generate and display FSH output, with a waiting spinner (light/dark theme) during processing.
|
Click Convert to FSH to generate and display FSH output, with a waiting spinner (light/dark theme) during processing.
|
||||||
If Fishing Trip is enabled, view the comparison report via the "Click here for SUSHI Validation" badge button.
|
If Fishing Trip is enabled, view the comparison report via the "Click here for SUSHI Validation" badge button.
|
||||||
Download the result as a .fsh file.
|
Download the result as a .fsh file.
|
||||||
|
Retrieve and Split Bundles
|
||||||
|
Navigate to Retrieve/Split Data (/retrieve-split-data).
|
||||||
|
|
||||||
Example Input:
|
Retrieve Bundles from Server:
|
||||||
{
|
|
||||||
"resourceType": "Patient",
|
|
||||||
"id": "banks-mia-leanne",
|
|
||||||
"meta": {
|
|
||||||
"profile": ["http://hl7.org.au/fhir/core/StructureDefinition/au-core-patient"]
|
|
||||||
},
|
|
||||||
"name": [
|
|
||||||
{
|
|
||||||
"family": "Banks",
|
|
||||||
"given": ["Mia", "Leanne"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
Example Output:
|
Enter the FHIR Server URL (defaults to the proxy if empty).
|
||||||
Profile: AUCorePatient
|
Select one or more Resource Types to retrieve (e.g., Patient, Observation).
|
||||||
Parent: Patient
|
Optionally check Fetch Referenced Resources.
|
||||||
* name 1..*
|
If checked, further optionally check Fetch Full Reference Bundles to retrieve entire bundles for each referenced type (e.g., all Patients if a Patient is referenced) instead of individual resources by ID.
|
||||||
* name.family 1..1
|
Click Retrieve Bundles.
|
||||||
* name.given 1..*
|
Monitor progress in the streaming log. A ZIP file containing the retrieved bundles/resources will be prepared for download.
|
||||||
|
Split Uploaded Bundles:
|
||||||
|
|
||||||
|
Upload a ZIP file containing FHIR bundles (JSON format).
|
||||||
|
Click Split Bundles.
|
||||||
|
A ZIP file containing individual resources extracted from the bundles will be prepared for download.
|
||||||
Explore FHIR Operations
|
Explore FHIR Operations
|
||||||
|
|
||||||
Navigate to FHIR UI Operations (/fhir-ui-operations).
|
Navigate to FHIR UI Operations (/fhir-ui-operations).
|
||||||
|
|
||||||
Toggle between local HAPI (/fhir) or a custom FHIR server.
|
Toggle between local HAPI (/fhir) or a custom FHIR server.
|
||||||
Click Fetch Metadata to load the server’s CapabilityStatement.
|
Click Fetch Metadata to load the server’s CapabilityStatement.
|
||||||
Select a resource type (e.g., Patient, Observation) or System to view operations:
|
Select a resource type (e.g., Patient, Observation) or System to view operations:
|
||||||
System operations: GET /metadata, POST /, GET /_history, GET/POST /$diff, POST /$reindex, POST /$expunge, etc.
|
System operations: GET /metadata, POST /, GET /_history, GET/POST /$diff, POST /$reindex, POST /$expunge, etc.
|
||||||
Resource operations: GET Patient/:id, POST Observation/_search, etc.
|
Resource operations: GET Patient/:id, POST Observation/_search, etc.
|
||||||
|
|
||||||
|
|
||||||
Use Try it out to input parameters or request bodies, then Execute to view results in JSON, XML, or narrative formats.
|
Use Try it out to input parameters or request bodies, then Execute to view results in JSON, XML, or narrative formats.
|
||||||
|
|
||||||
|
### Configure Embedded HAPI FHIR Server (Standalone Mode)
|
||||||
|
For users running the **Standalone version**, which includes an embedded HAPI FHIR server.
|
||||||
|
1. Navigate to **Configure HAPI FHIR** (`/config-hapi`).
|
||||||
|
2. The page displays the content of the HAPI FHIR server's `application.yaml` file.
|
||||||
|
3. You can edit the configuration directly in the text area.
|
||||||
|
* *Caution: Incorrect modifications can break the HAPI FHIR server.*
|
||||||
|
4. Click **Save Configuration** to apply your changes to the `application.yaml` file.
|
||||||
|
5. Click **Restart Tomcat** to restart the HAPI FHIR server and load the new configuration. The restart process may take a few moments.
|
||||||
|
|
||||||
API Usage
|
API Usage
|
||||||
Import IG
|
Import IG
|
||||||
|
Bash
|
||||||
|
|
||||||
curl -X POST http://localhost:5000/api/import-ig \
|
curl -X POST http://localhost:5000/api/import-ig \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{"package_name": "hl7.fhir.au.core", "version": "1.1.0-preview", "api_key": "your-api-key"}'
|
-H "X-API-Key: your-api-key" \
|
||||||
|
-d '{"package_name": "hl7.fhir.au.core", "version": "1.1.0-preview", "dependency_mode": "recursive"}'
|
||||||
|
Returns complies_with_profiles, imposed_profiles, and duplicate_packages_present info.
|
||||||
|
|
||||||
|
### Refresh Package Cache (Background Task)
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:5000/api/refresh-cache-task \
|
||||||
|
-H "X-API-Key: your-api-key"
|
||||||
|
|
||||||
Returns complies_with_profiles, imposed_profiles, and duplicate info.
|
|
||||||
Push IG
|
Push IG
|
||||||
|
Bash
|
||||||
|
|
||||||
curl -X POST http://localhost:5000/api/push-ig \
|
curl -X POST http://localhost:5000/api/push-ig \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-H "Accept: application/x-ndjson" \
|
-H "Accept: application/x-ndjson" \
|
||||||
-d '{"package_name": "hl7.fhir.au.core", "version": "1.1.0-preview", "fhir_server_url": "http://localhost:8080/fhir", "include_dependencies": true, "api_key": "your-api-key"}'
|
-H "X-API-Key: your-api-key" \
|
||||||
|
-d '{
|
||||||
|
"package_name": "hl7.fhir.au.core",
|
||||||
|
"version": "1.1.0-preview",
|
||||||
|
"fhir_server_url": "http://localhost:8080/fhir",
|
||||||
|
"include_dependencies": true,
|
||||||
|
"force_upload": false,
|
||||||
|
"dry_run": false,
|
||||||
|
"verbose": false,
|
||||||
|
"auth_type": "none"
|
||||||
|
}'
|
||||||
|
Returns a streaming NDJSON response with progress and final summary.
|
||||||
|
|
||||||
|
Upload Test Data
|
||||||
|
Bash
|
||||||
|
|
||||||
|
curl -X POST http://localhost:5000/api/upload-test-data \
|
||||||
|
-H "X-API-Key: your-api-key" \
|
||||||
|
-H "Accept: application/x-ndjson" \
|
||||||
|
-F "fhir_server_url=http://your-fhir-server/fhir" \
|
||||||
|
-F "auth_type=bearerToken" \
|
||||||
|
-F "auth_token=YOUR_TOKEN" \
|
||||||
|
-F "upload_mode=individual" \
|
||||||
|
-F "error_handling=continue" \
|
||||||
|
-F "validate_before_upload=true" \
|
||||||
|
-F "validation_package_id=hl7.fhir.r4.core#4.0.1" \
|
||||||
|
-F "use_conditional_uploads=true" \
|
||||||
|
-F "test_data_files=@/path/to/your/patient.json" \
|
||||||
|
-F "test_data_files=@/path/to/your/observations.zip"
|
||||||
|
Returns a streaming NDJSON response with progress and final summary. Uses multipart/form-data for file uploads.
|
||||||
|
|
||||||
|
Retrieve Bundles
|
||||||
|
Bash
|
||||||
|
|
||||||
|
curl -X POST http://localhost:5000/api/retrieve-bundles \
|
||||||
|
-H "X-API-Key: your-api-key" \
|
||||||
|
-H "Accept: application/x-ndjson" \
|
||||||
|
-F "fhir_server_url=http://your-fhir-server/fhir" \
|
||||||
|
-F "resources=Patient" \
|
||||||
|
-F "resources=Observation" \
|
||||||
|
-F "validate_references=true" \
|
||||||
|
-F "fetch_reference_bundles=false"
|
||||||
|
Returns a streaming NDJSON response with progress. The X-Zip-Path header in the final response part will contain the path to download the ZIP archive (e.g., /tmp/retrieved_bundles_datetime.zip).
|
||||||
|
|
||||||
|
Split Bundles
|
||||||
|
Bash
|
||||||
|
|
||||||
|
curl -X POST http://localhost:5000/api/split-bundles \
|
||||||
|
-H "X-API-Key: your-api-key" \
|
||||||
|
-H "Accept: application/x-ndjson" \
|
||||||
|
-F "split_bundle_zip_path=@/path/to/your/bundles.zip"
|
||||||
|
Returns a streaming NDJSON response. The X-Zip-Path header in the final response part will contain the path to download the ZIP archive of split resources.
|
||||||
|
|
||||||
Validates resources against imposed profiles (if enabled).
|
|
||||||
Validate Resource/Bundle
|
Validate Resource/Bundle
|
||||||
Not yet exposed via API; use the UI at /validate-sample.
|
Not yet exposed via API; use the UI at /validate-sample.
|
||||||
|
|
||||||
Configuration Options
|
Configuration Options
|
||||||
|
Located in app.py:
|
||||||
|
|
||||||
VALIDATE_IMPOSED_PROFILES:
|
VALIDATE_IMPOSED_PROFILES: (Default: True) Validates resources against imposed profiles during push.
|
||||||
|
DISPLAY_PROFILE_RELATIONSHIPS: (Default: True) Shows compliesWithProfile and imposeProfile in the UI.
|
||||||
Default: True
|
FHIR_PACKAGES_DIR: (Default: /app/instance/fhir_packages) Stores .tgz packages and metadata.
|
||||||
|
UPLOAD_FOLDER: (Default: /app/static/uploads) Stores GoFSH output files and FSH comparison reports.
|
||||||
Validates resources against imposed profiles during push.
|
SECRET_KEY: Required for CSRF protection and sessions. Set via environment variable or directly.
|
||||||
|
API_KEY: Required for API authentication. Set via environment variable or directly.
|
||||||
Set to False to skip:
|
MAX_CONTENT_LENGTH: (Default: Flask default) Max size for HTTP request body (e.g., 16 * 1024 * 1024 for 16MB). Important for large uploads.
|
||||||
app.config['VALIDATE_IMPOSED_PROFILES'] = False
|
MAX_FORM_PARTS: (Default: Werkzeug default, often 1000) Default max number of form parts. Overridden for /api/upload-test-data by CustomFormDataParser.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
DISPLAY_PROFILE_RELATIONSHIPS:
|
|
||||||
|
|
||||||
Default: True
|
|
||||||
|
|
||||||
Shows compliesWithProfile and imposeProfile in the UI.
|
|
||||||
|
|
||||||
Set to False to hide:
|
|
||||||
app.config['DISPLAY_PROFILE_RELATIONSHIPS'] = False
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
FHIR_PACKAGES_DIR:
|
|
||||||
|
|
||||||
Default: /app/instance/fhir_packages
|
|
||||||
Stores .tgz packages and metadata.
|
|
||||||
|
|
||||||
|
|
||||||
UPLOAD_FOLDER:
|
|
||||||
|
|
||||||
Default: /app/static/uploads
|
|
||||||
Stores GoFSH output files and FSH comparison reports.
|
|
||||||
|
|
||||||
|
|
||||||
SECRET_KEY:
|
|
||||||
|
|
||||||
Required for CSRF protection and sessions:
|
|
||||||
app.config['SECRET_KEY'] = 'your-secure-secret-key'
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
API_KEY:
|
|
||||||
|
|
||||||
Required for API authentication:
|
|
||||||
app.config['API_KEY'] = 'your-api-key'
|
|
||||||
|
|
||||||
|
|
||||||
|
### Get HAPI FHIR Configuration (Standalone Mode)
|
||||||
|
```bash
|
||||||
|
curl -X GET http://localhost:5000/api/config \
|
||||||
|
-H "X-API-Key: your-api-key"
|
||||||
|
|
||||||
|
Save HAPI FHIR Configuration:
|
||||||
|
curl -X POST http://localhost:5000/api/config \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "X-API-Key: your-api-key" \
|
||||||
|
-d '{"your_yaml_key": "your_value", ...}' # Send the full YAML content as JSON
|
||||||
|
|
||||||
|
Restart HAPI FHIR Server:
|
||||||
|
curl -X POST http://localhost:5000/api/restart-tomcat \
|
||||||
|
-H "X-API-Key: your-api-key"
|
||||||
|
|
||||||
Testing
|
Testing
|
||||||
The project includes a test suite covering UI, API, database, file operations, and security.
|
The project includes a test suite covering UI, API, database, file operations, and security.
|
||||||
Test Prerequisites
|
|
||||||
|
Test Prerequisites:
|
||||||
|
|
||||||
pytest: For running tests.
|
pytest: For running tests.
|
||||||
pytest-mock: For mocking dependencies.
|
pytest-mock: For mocking dependencies. Install: pip install pytest pytest-mock
|
||||||
|
Running Tests:
|
||||||
|
|
||||||
Install:
|
Bash
|
||||||
pip install pytest pytest-mock
|
|
||||||
|
|
||||||
Running Tests
|
|
||||||
cd <project folder>
|
cd <project folder>
|
||||||
pytest tests/test_app.py -v
|
pytest tests/test_app.py -v
|
||||||
|
Test Coverage:
|
||||||
|
|
||||||
Test Coverage
|
UI Pages: Homepage, Import IG, Manage IGs, Push IGs, Validate Sample, View Processed IG, FSH Converter, Upload Test Data, Retrieve/Split Data.
|
||||||
|
API Endpoints: POST /api/import-ig, POST /api/push-ig, GET /get-structure, GET /get-example, POST /api/upload-test-data, POST /api/retrieve-bundles, POST /api/split-bundles.
|
||||||
UI Pages: Homepage, Import IG, Manage IGs, Push IGs, Validate Sample, View Processed IG, FSH Converter.
|
|
||||||
API Endpoints: POST /api/import-ig, POST /api/push-ig, GET /get-structure, GET /get-example.
|
|
||||||
Database: IG processing, unloading, viewing.
|
Database: IG processing, unloading, viewing.
|
||||||
File Operations: Package processing, deletion, FSH output.
|
File Operations: Package processing, deletion, FSH output, ZIP handling.
|
||||||
Security: CSRF protection, flash messages, secret key.
|
Security: CSRF protection, flash messages, secret key.
|
||||||
FSH Converter: Form submission, file/text input, GoFSH execution, Fishing Trip comparison.
|
FSH Converter: Form submission, file/text input, GoFSH execution, Fishing Trip comparison.
|
||||||
|
Upload Test Data: Parsing, dependency graph, sorting, upload modes, validation, conditional uploads.
|
||||||
Example Test Output
|
|
||||||
================================================================ test session starts =================================================================
|
|
||||||
platform linux -- Python 3.12, pytest-8.3.5, pluggy-1.5.0
|
|
||||||
rootdir: /app/tests
|
|
||||||
collected 27 items
|
|
||||||
|
|
||||||
test_app.py::TestFHIRFlareIGToolkit::test_homepage PASSED [ 3%]
|
|
||||||
test_app.py::TestFHIRFlareIGToolkit::test_import_ig_page PASSED [ 7%]
|
|
||||||
test_app.py::TestFHIRFlareIGToolkit::test_fsh_converter_page PASSED [ 11%]
|
|
||||||
...
|
|
||||||
test_app.py::TestFHIRFlareIGToolkit::test_validate_sample_success PASSED [ 88%]
|
|
||||||
============================================================= 27 passed in 1.23s ==============================================================
|
|
||||||
|
|
||||||
Troubleshooting Tests
|
|
||||||
|
|
||||||
ModuleNotFoundError: Ensure app.py, services.py, forms.py are in /app/.
|
|
||||||
TemplateNotFound: Verify templates are in /app/templates/.
|
|
||||||
Database Errors: Ensure instance/fhir_ig.db is writable (chmod 777 instance).
|
|
||||||
Mock Failures: Check tests/test_app.py for correct mocking.
|
|
||||||
|
|
||||||
Development Notes
|
Development Notes
|
||||||
Background
|
Background
|
||||||
The toolkit addresses the need for a comprehensive FHIR IG management tool, with recent enhancements for resource validation, FSH conversion with advanced GoFSH features, and flexible versioning, making it a versatile platform for FHIR developers.
|
The toolkit addresses the need for a comprehensive FHIR IG management tool, with recent enhancements for resource validation, FSH conversion with advanced GoFSH features, flexible versioning, improved IG pushing, dependency-aware test data uploading, and bundle retrieval/splitting, making it a versatile platform for FHIR developers.
|
||||||
Technical Decisions
|
|
||||||
|
|
||||||
|
Technical Decisions
|
||||||
Flask: Lightweight and flexible for web development.
|
Flask: Lightweight and flexible for web development.
|
||||||
SQLite: Simple for development; consider PostgreSQL for production.
|
SQLite: Simple for development; consider PostgreSQL for production.
|
||||||
Bootstrap 5.3.3: Responsive UI with custom styling for duplicates, FSH output, and waiting spinner.
|
Bootstrap 5.3.3: Responsive UI with custom styling.
|
||||||
Lottie-Web: Renders themed animations for FSH conversion waiting spinner.
|
Lottie-Web: Renders themed animations for FSH conversion waiting spinner.
|
||||||
GoFSH/SUSHI: Integrated via Node.js for advanced FSH conversion and round-trip validation.
|
GoFSH/SUSHI: Integrated via Node.js for advanced FSH conversion and round-trip validation.
|
||||||
Docker: Ensures consistent deployment with Flask and HAPI FHIR.
|
Docker: Ensures consistent deployment with Flask and HAPI FHIR.
|
||||||
Flexible Versioning: Supports non-standard IG versions (e.g., -preview, -ballot).
|
Flexible Versioning: Supports non-standard IG versions (e.g., -preview, -ballot).
|
||||||
Live Console: Real-time feedback for complex operations.
|
Live Console/Streaming: Real-time feedback for complex operations (Push, Upload Test Data, FSH, Retrieve Bundles).
|
||||||
Validation: Alpha feature with ongoing FHIRPath improvements.
|
Validation: Alpha feature with ongoing FHIRPath improvements.
|
||||||
|
Dependency Management: Uses topological sort for Upload Test Data feature.
|
||||||
|
Form Parsing: Uses custom Werkzeug parser for Upload Test Data to handle large numbers of files.
|
||||||
Recent Updates
|
Recent Updates
|
||||||
|
* Enhanced package search page with caching, detailed views (dependencies, dependents, version history), and background cache refresh.
|
||||||
|
Upload Test Data Enhancements (April 2025):
|
||||||
|
Added optional Pre-Upload Validation against selected IG profiles.
|
||||||
|
Added optional Conditional Uploads (GET + POST/PUT w/ If-Match) for individual mode.
|
||||||
|
Implemented robust XML parsing using fhir.resources library (when available).
|
||||||
|
Fixed 413 Request Entity Too Large errors for large file counts using a custom Werkzeug FormDataParser.
|
||||||
|
Path: templates/upload_test_data.html, app.py, services.py, forms.py.
|
||||||
|
Push IG Enhancements (April 2025):
|
||||||
|
Added semantic comparison to skip uploading identical resources.
|
||||||
|
Added "Force Upload" option to bypass comparison.
|
||||||
|
Improved handling of canonical resources (search before PUT/POST).
|
||||||
|
Added filtering by specific files to skip during push.
|
||||||
|
More detailed summary report in stream response.
|
||||||
|
Path: templates/cp_push_igs.html, app.py, services.py.
|
||||||
Waiting Spinner for FSH Converter (April 2025):
|
Waiting Spinner for FSH Converter (April 2025):
|
||||||
Added a themed (light/dark) Lottie animation spinner during FSH execution to indicate processing.
|
Added a themed (light/dark) Lottie animation spinner during FSH execution.
|
||||||
Path: templates/fsh_converter.html, static/animations/loading-dark.json, static/animations/loading-light.json, static/js/lottie-web.min.js.
|
Path: templates/fsh_converter.html, static/animations/, static/js/lottie-web.min.js.
|
||||||
|
|
||||||
|
|
||||||
Advanced FSH Converter (April 2025):
|
Advanced FSH Converter (April 2025):
|
||||||
Added support for GoFSH advanced options: --fshing-trip (round-trip validation with SUSHI), --dependency (additional packages), --indent (indented rules), --meta-profile (only-one, first, none), --alias-file (custom aliases), --no-alias (disable alias generation).
|
Added support for GoFSH advanced options: --fshing-trip, --dependency, --indent, --meta-profile, --alias-file, --no-alias.
|
||||||
Displays Fishing Trip comparison reports via a badge button.
|
Displays Fishing Trip comparison reports.
|
||||||
Path: templates/fsh_converter.html, app.py, services.py, forms.py.
|
Path: templates/fsh_converter.html, app.py, services.py, forms.py.
|
||||||
|
(New) Retrieve and Split Data (May 2025):
|
||||||
|
Added UI and API for retrieving bundles from a FHIR server by resource type.
|
||||||
FSH Converter (April 2025):
|
Added options to fetch referenced resources (individually or as full type bundles).
|
||||||
Added /fsh-converter page for FHIR to FSH conversion using GoFSH.
|
Added functionality to split uploaded ZIP files of bundles into individual resources.
|
||||||
Path: templates/fsh_converter.html, app.py, services.py, forms.py.
|
Streaming log for retrieval and ZIP download for results.
|
||||||
|
Paths: templates/retrieve_split_data.html, app.py, services.py, forms.py.
|
||||||
|
|
||||||
Favicon Fix (April 2025):
|
|
||||||
Resolved 404 for /favicon.ico on /fsh-converter by ensuring static/favicon.ico is served.
|
|
||||||
Added fallback /favicon.ico route in app.py.
|
|
||||||
|
|
||||||
|
|
||||||
Menu Item (April 2025):
|
|
||||||
Added “FSH Converter” to the navbar in base.html.
|
|
||||||
|
|
||||||
|
|
||||||
UPLOAD_FOLDER Fix (April 2025):
|
|
||||||
Fixed 500 error on /fsh-converter by setting app.config['UPLOAD_FOLDER'] = '/app/static/uploads'.
|
|
||||||
|
|
||||||
|
|
||||||
Validation (April 2025):
|
|
||||||
Alpha support for validating resources/bundles in /validate-sample.
|
|
||||||
Path: templates/validate_sample.html, app.py, services.py.
|
|
||||||
|
|
||||||
|
|
||||||
CSRF Protection: Fixed missing CSRF tokens in cp_downloaded_igs.html, cp_push_igs.html.
|
|
||||||
Version Support: Added flexible version formats (e.g., 1.1.0-preview) in forms.py.
|
|
||||||
|
|
||||||
Known Issues and Workarounds
|
Known Issues and Workarounds
|
||||||
|
Favicon 404: Clear browser cache or verify /app/static/favicon.ico.
|
||||||
Favicon 404: Clear browser cache or verify /app/static/favicon.ico:
|
|
||||||
docker exec -it <container_name> curl http://localhost:5000/static/favicon.ico
|
|
||||||
|
|
||||||
|
|
||||||
CSRF Errors: Set FLASK_SECRET_KEY and ensure {{ form.hidden_tag() }} in forms.
|
CSRF Errors: Set FLASK_SECRET_KEY and ensure {{ form.hidden_tag() }} in forms.
|
||||||
|
|
||||||
Import Fails: Check package name/version and connectivity.
|
Import Fails: Check package name/version and connectivity.
|
||||||
|
Validation Accuracy: Alpha feature; report issues to GitHub (remove PHI).
|
||||||
Validation Accuracy: Alpha feature; FHIRPath may miss complex constraints. Report issues to GitHub (remove PHI).
|
|
||||||
|
|
||||||
Package Parsing: Non-standard .tgz filenames may parse incorrectly. Fallback uses name-only parsing.
|
Package Parsing: Non-standard .tgz filenames may parse incorrectly. Fallback uses name-only parsing.
|
||||||
|
Permissions: Ensure instance/ and static/uploads/ are writable.
|
||||||
Permissions: Ensure instance/ and static/uploads/ are writable:
|
GoFSH/SUSHI Errors: Check ./logs/flask_err.log for ERROR:services:GoFSH failed. Ensure valid FHIR inputs and SUSHI installation.
|
||||||
chmod -R 777 instance static/uploads logs
|
Upload Test Data XML Parsing: Relies on fhir.resources library for full validation; basic parsing used as fallback. Complex XML structures might not be fully analyzed for dependencies with basic parsing. Prefer JSON for reliable dependency analysis.
|
||||||
|
413 Request Entity Too Large: Primarily handled by CustomFormDataParser for /api/upload-test-data. Check the parser's max_form_parts limit if still occurring. MAX_CONTENT_LENGTH in app.py controls overall size. Reverse proxy limits (client_max_body_size in Nginx) might also apply.
|
||||||
|
|
||||||
GoFSH/SUSHI Errors: Check ./logs/flask_err.log for ERROR:services:GoFSH failed. Ensure valid FHIR inputs and SUSHI installation:
|
|
||||||
docker exec -it <container_name> sushi --version
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Future Improvements
|
Future Improvements
|
||||||
|
Upload Test Data: Improve XML parsing further (direct XML->fhir.resource object if possible), add visual progress bar, add upload order preview, implement transaction bundle size splitting, add 'Clear Target Server' option (with confirmation).
|
||||||
Validation: Enhance FHIRPath for complex constraints; add API endpoint.
|
Validation: Enhance FHIRPath for complex constraints; add API endpoint.
|
||||||
Sorting: Sort IG versions in /view-igs (e.g., ascending).
|
Sorting: Sort IG versions in /view-igs (e.g., ascending).
|
||||||
Duplicate Resolution: Options to keep latest version or merge resources.
|
Duplicate Resolution: Options to keep latest version or merge resources.
|
||||||
@ -479,22 +606,22 @@ Production Database: Support PostgreSQL.
|
|||||||
Error Reporting: Detailed validation error paths in the UI.
|
Error Reporting: Detailed validation error paths in the UI.
|
||||||
FSH Enhancements: Add API endpoint for FSH conversion; support inline instance construction.
|
FSH Enhancements: Add API endpoint for FSH conversion; support inline instance construction.
|
||||||
FHIR Operations: Add complex parameter support (e.g., /$diff with left/right).
|
FHIR Operations: Add complex parameter support (e.g., /$diff with left/right).
|
||||||
Spinner Enhancements: Customize spinner animation speed or size.
|
Retrieve/Split Data: Add option to filter resources during retrieval (e.g., by date, specific IDs).
|
||||||
|
|
||||||
Completed Items
|
Completed Items
|
||||||
|
Testing suite with basic coverage.
|
||||||
Testing suite with 27 cases.
|
|
||||||
API endpoints for POST /api/import-ig and POST /api/push-ig.
|
API endpoints for POST /api/import-ig and POST /api/push-ig.
|
||||||
Flexible versioning (-preview, -ballot).
|
Flexible versioning (-preview, -ballot).
|
||||||
CSRF fixes for forms.
|
CSRF fixes for forms.
|
||||||
Resource validation UI (alpha).
|
Resource validation UI (alpha).
|
||||||
FSH Converter with advanced GoFSH features and waiting spinner.
|
FSH Converter with advanced GoFSH features and waiting spinner.
|
||||||
|
Push IG enhancements (force upload, semantic comparison, canonical handling, skip files).
|
||||||
|
Upload Test Data feature with dependency sorting, multiple upload modes, pre-upload validation, conditional uploads, robust XML parsing, and fix for large file counts.
|
||||||
|
Retrieve and Split Data functionality with reference fetching and ZIP download.
|
||||||
Far-Distant Improvements
|
Far-Distant Improvements
|
||||||
|
|
||||||
Cache Service: Use Redis for IG metadata caching.
|
Cache Service: Use Redis for IG metadata caching.
|
||||||
Database Optimization: Composite index on ProcessedIg.package_name and ProcessedIg.version.
|
Database Optimization: Composite index on ProcessedIg.package_name and ProcessedIg.version.
|
||||||
|
|
||||||
|
|
||||||
Directory Structure
|
Directory Structure
|
||||||
FHIRFLARE-IG-Toolkit/
|
FHIRFLARE-IG-Toolkit/
|
||||||
├── app.py # Main Flask application
|
├── app.py # Main Flask application
|
||||||
@ -506,7 +633,7 @@ FHIRFLARE-IG-Toolkit/
|
|||||||
├── README.md # Project documentation
|
├── README.md # Project documentation
|
||||||
├── requirements.txt # Python dependencies
|
├── requirements.txt # Python dependencies
|
||||||
├── Run.bat # Windows script for running Docker
|
├── Run.bat # Windows script for running Docker
|
||||||
├── services.py # Logic for IG import, processing, validation, pushing, and FSH conversion
|
├── services.py # Logic for IG import, processing, validation, pushing, FSH conversion, test data upload, retrieve/split
|
||||||
├── supervisord.conf # Supervisor configuration
|
├── supervisord.conf # Supervisor configuration
|
||||||
├── hapi-fhir-Setup/
|
├── hapi-fhir-Setup/
|
||||||
│ ├── README.md # HAPI FHIR setup instructions
|
│ ├── README.md # HAPI FHIR setup instructions
|
||||||
@ -517,24 +644,7 @@ FHIRFLARE-IG-Toolkit/
|
|||||||
│ ├── fhir_ig.db # SQLite database
|
│ ├── fhir_ig.db # SQLite database
|
||||||
│ ├── fhir_ig.db.old # Database backup
|
│ ├── fhir_ig.db.old # Database backup
|
||||||
│ └── fhir_packages/ # Stored IG packages and metadata
|
│ └── fhir_packages/ # Stored IG packages and metadata
|
||||||
│ ├── hl7.fhir.au.base-5.1.0-preview.metadata.json
|
│ ├── ... (example packages) ...
|
||||||
│ ├── hl7.fhir.au.base-5.1.0-preview.tgz
|
|
||||||
│ ├── hl7.fhir.au.core-1.1.0-preview.metadata.json
|
|
||||||
│ ├── hl7.fhir.au.core-1.1.0-preview.tgz
|
|
||||||
│ ├── hl7.fhir.r4.core-4.0.1.metadata.json
|
|
||||||
│ ├── hl7.fhir.r4.core-4.0.1.tgz
|
|
||||||
│ ├── hl7.fhir.uv.extensions.r4-5.2.0.metadata.json
|
|
||||||
│ ├── hl7.fhir.uv.extensions.r4-5.2.0.tgz
|
|
||||||
│ ├── hl7.fhir.uv.ipa-1.0.0.metadata.json
|
|
||||||
│ ├── hl7.fhir.uv.ipa-1.0.0.tgz
|
|
||||||
│ ├── hl7.fhir.uv.smart-app-launch-2.0.0.metadata.json
|
|
||||||
│ ├── hl7.fhir.uv.smart-app-launch-2.0.0.tgz
|
|
||||||
│ ├── hl7.fhir.uv.smart-app-launch-2.1.0.metadata.json
|
|
||||||
│ ├── hl7.fhir.uv.smart-app-launch-2.1.0.tgz
|
|
||||||
│ ├── hl7.terminology.r4-5.0.0.metadata.json
|
|
||||||
│ ├── hl7.terminology.r4-5.0.0.tgz
|
|
||||||
│ ├── hl7.terminology.r4-6.2.0.metadata.json
|
|
||||||
│ └── hl7.terminology.r4-6.2.0.tgz
|
|
||||||
├── logs/
|
├── logs/
|
||||||
│ ├── flask.log # Flask application logs
|
│ ├── flask.log # Flask application logs
|
||||||
│ ├── flask_err.log # Flask error logs
|
│ ├── flask_err.log # Flask error logs
|
||||||
@ -551,15 +661,9 @@ FHIRFLARE-IG-Toolkit/
|
|||||||
│ ├── js/
|
│ ├── js/
|
||||||
│ │ └── lottie-web.min.js # Lottie library for spinner
|
│ │ └── lottie-web.min.js # Lottie library for spinner
|
||||||
│ └── uploads/
|
│ └── uploads/
|
||||||
│ ├── output.fsh # Generated FSH output
|
│ ├── output.fsh # Generated FSH output (temp location)
|
||||||
│ └── fsh_output/
|
│ └── fsh_output/ # GoFSH output directory
|
||||||
│ ├── sushi-config.yaml # SUSHI configuration
|
│ ├── ... (example GoFSH output) ...
|
||||||
│ └── input/
|
|
||||||
│ └── fsh/
|
|
||||||
│ ├── aliases.fsh # FSH aliases
|
|
||||||
│ ├── index.txt # FSH index
|
|
||||||
│ └── instances/
|
|
||||||
│ └── banks-mia-leanne.fsh # Example FSH instance
|
|
||||||
├── templates/
|
├── templates/
|
||||||
│ ├── base.html # Base template
|
│ ├── base.html # Base template
|
||||||
│ ├── cp_downloaded_igs.html # UI for managing IGs
|
│ ├── cp_downloaded_igs.html # UI for managing IGs
|
||||||
@ -570,43 +674,31 @@ FHIRFLARE-IG-Toolkit/
|
|||||||
│ ├── fsh_converter.html # UI for FSH conversion
|
│ ├── fsh_converter.html # UI for FSH conversion
|
||||||
│ ├── import_ig.html # UI for importing IGs
|
│ ├── import_ig.html # UI for importing IGs
|
||||||
│ ├── index.html # Homepage
|
│ ├── index.html # Homepage
|
||||||
|
│ ├── retrieve_split_data.html # UI for Retrieve and Split Data
|
||||||
|
│ ├── upload_test_data.html # UI for Uploading Test Data
|
||||||
│ ├── validate_sample.html # UI for validating resources/bundles
|
│ ├── validate_sample.html # UI for validating resources/bundles
|
||||||
|
│ ├── config_hapi.html # UI for HAPI FHIR Configuration
|
||||||
│ └── _form_helpers.html # Form helper macros
|
│ └── _form_helpers.html # Form helper macros
|
||||||
├── tests/
|
├── tests/
|
||||||
│ └── test_app.py # Test suite with 27 cases
|
│ └── test_app.py # Test suite
|
||||||
└── hapi-fhir-jpaserver/ # HAPI FHIR server resources
|
└── hapi-fhir-jpaserver/ # HAPI FHIR server resources (if Standalone)
|
||||||
|
|
||||||
Contributing
|
Contributing
|
||||||
|
|
||||||
Fork the repository.
|
Fork the repository.
|
||||||
Create a feature branch (git checkout -b feature/your-feature).
|
Create a feature branch (git checkout -b feature/your-feature).
|
||||||
Commit changes (git commit -m "Add your feature").
|
Commit changes (git commit -m "Add your feature").
|
||||||
Push to your branch (git push origin feature/your-feature).
|
Push to your branch (git push origin feature/your-feature).
|
||||||
Open a Pull Request.
|
Open a Pull Request.
|
||||||
|
|
||||||
Ensure code follows PEP 8 and includes tests in tests/test_app.py.
|
Ensure code follows PEP 8 and includes tests in tests/test_app.py.
|
||||||
|
|
||||||
Troubleshooting
|
Troubleshooting
|
||||||
|
Favicon 404: Clear browser cache or verify /app/static/favicon.ico: docker exec -it <container_name> curl http://localhost:5000/static/favicon.ico
|
||||||
Favicon 404: Clear browser cache or verify /app/static/favicon.ico:
|
|
||||||
docker exec -it <container_name> curl http://localhost:5000/static/favicon.ico
|
|
||||||
|
|
||||||
|
|
||||||
CSRF Errors: Set FLASK_SECRET_KEY and ensure {{ form.hidden_tag() }} in forms.
|
CSRF Errors: Set FLASK_SECRET_KEY and ensure {{ form.hidden_tag() }} in forms.
|
||||||
|
|
||||||
Import Fails: Check package name/version and connectivity.
|
Import Fails: Check package name/version and connectivity.
|
||||||
|
|
||||||
Validation Accuracy: Alpha feature; report issues to GitHub (remove PHI).
|
Validation Accuracy: Alpha feature; report issues to GitHub (remove PHI).
|
||||||
|
Package Parsing: Non-standard .tgz filenames may parse incorrectly. Fallback uses name-only parsing.
|
||||||
Package Parsing: Non-standard .tgz filenamesgrass may parse incorrectly. Fallback uses name-only parsing.
|
Permissions: Ensure instance/ and static/uploads/ are writable: chmod -R 777 instance static/uploads logs
|
||||||
|
GoFSH/SUSHI Errors: Check ./logs/flask_err.log for ERROR:services:GoFSH failed. Ensure valid FHIR inputs and SUSHI installation: docker exec -it <container_name> sushi --version
|
||||||
Permissions: Ensure instance/ and static/uploads/ are writable:
|
413 Request Entity Too Large: Increase MAX_CONTENT_LENGTH and MAX_FORM_PARTS in app.py. If using a reverse proxy (e.g., Nginx), increase its client_max_body_size setting as well. Ensure the application/container is fully restarted/rebuilt.
|
||||||
chmod -R 777 instance static/uploads logs
|
|
||||||
|
|
||||||
|
|
||||||
GoFSH/SUSHI Errors: Check ./logs/flask_err.log for ERROR:services:GoFSH failed. Ensure valid FHIR inputs and SUSHI installation:
|
|
||||||
docker exec -it <container_name> sushi --version
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
License
|
License
|
||||||
Licensed under the Apache 2.0 License. See LICENSE.md for details.
|
Licensed under the Apache 2.0 License. See LICENSE.md for details.
|
||||||
151
README_INTEGRATION FHIRVINE as Moduel in FLARE.md
Normal file
151
README_INTEGRATION FHIRVINE as Moduel in FLARE.md
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
# Integrating FHIRVINE as a Module in FHIRFLARE
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
FHIRFLARE is a Flask-based FHIR Implementation Guide (IG) toolkit for managing and validating FHIR packages. This guide explains how to integrate FHIRVINE—a SMART on FHIR proxy—as a module within FHIRFLARE, enabling OAuth2 authentication and FHIR request proxying directly in the application. This modular approach embeds FHIRVINE’s functionality into FHIRFLARE, avoiding the need for a separate proxy service.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- FHIRFLARE repository cloned: `https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit`.
|
||||||
|
- FHIRVINE repository cloned: `<fhirvine-repository-url>`.
|
||||||
|
- Python 3.11 and dependencies installed (`requirements.txt` from both projects).
|
||||||
|
- A FHIR server (e.g., `http://hapi.fhir.org/baseR4`).
|
||||||
|
|
||||||
|
## Integration Steps
|
||||||
|
|
||||||
|
### 1. Prepare FHIRFLARE Structure
|
||||||
|
|
||||||
|
Ensure FHIRFLARE’s file structure supports modular integration. It should look like:
|
||||||
|
|
||||||
|
```
|
||||||
|
FHIRFLARE-IG-Toolkit/
|
||||||
|
├── app.py
|
||||||
|
├── services.py
|
||||||
|
├── templates/
|
||||||
|
├── static/
|
||||||
|
└── requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Copy FHIRVINE Files into FHIRFLARE
|
||||||
|
|
||||||
|
FHIRVINE’s core functionality (OAuth2 proxy, app registration) will be integrated as a Flask Blueprint.
|
||||||
|
|
||||||
|
- **Copy Files**:
|
||||||
|
|
||||||
|
- Copy `smart_proxy.py`, `forms.py`, `models.py`, and `app.py` (relevant parts) from FHIRVINE into a new `fhirvine/` directory in FHIRFLARE:
|
||||||
|
|
||||||
|
```
|
||||||
|
FHIRFLARE-IG-Toolkit/
|
||||||
|
├── fhirvine/
|
||||||
|
│ ├── smart_proxy.py
|
||||||
|
│ ├── forms.py
|
||||||
|
│ ├── models.py
|
||||||
|
│ └── __init__.py
|
||||||
|
```
|
||||||
|
|
||||||
|
- Copy FHIRVINE’s templates (e.g., `app_gallery/`, `configure/`, `test_client.html`) into `FHIRFLARE-IG-Toolkit/templates/` while maintaining their folder structure.
|
||||||
|
|
||||||
|
- **Add Dependencies**:
|
||||||
|
|
||||||
|
- Add FHIRVINE’s dependencies to `requirements.txt` (e.g., `authlib`, `flasgger`, `flask-sqlalchemy`).
|
||||||
|
|
||||||
|
### 3. Modify FHIRVINE Code as a Module
|
||||||
|
|
||||||
|
- **Create Blueprint in** `fhirvine/__init__.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from flask import Blueprint
|
||||||
|
|
||||||
|
fhirvine_bp = Blueprint('fhirvine', __name__, template_folder='templates')
|
||||||
|
|
||||||
|
from .smart_proxy import *
|
||||||
|
```
|
||||||
|
|
||||||
|
This registers FHIRVINE as a Flask Blueprint.
|
||||||
|
|
||||||
|
- **Update** `smart_proxy.py`:
|
||||||
|
|
||||||
|
- Replace direct `app.route` decorators with `fhirvine_bp.route`. For example:
|
||||||
|
|
||||||
|
```python
|
||||||
|
@fhirvine_bp.route('/authorize', methods=['GET', 'POST'])
|
||||||
|
def authorize():
|
||||||
|
# Existing authorization logic
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Integrate FHIRVINE Blueprint into FHIRFLARE
|
||||||
|
|
||||||
|
- **Update** `app.py` **in FHIRFLARE**:
|
||||||
|
|
||||||
|
- Import and register the FHIRVINE Blueprint:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from fhirvine import fhirvine_bp
|
||||||
|
from fhirvine.models import database, RegisteredApp, OAuthToken, AuthorizationCode, Configuration
|
||||||
|
from fhirvine.smart_proxy import configure_oauth
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config.from_mapping(
|
||||||
|
SECRET_KEY='your-secure-random-key',
|
||||||
|
SQLALCHEMY_DATABASE_URI='sqlite:////app/instance/fhirflare.db',
|
||||||
|
SQLALCHEMY_TRACK_MODIFICATIONS=False,
|
||||||
|
FHIR_SERVER_URL='http://hapi.fhir.org/baseR4',
|
||||||
|
PROXY_TIMEOUT=10,
|
||||||
|
TOKEN_DURATION=3600,
|
||||||
|
REFRESH_TOKEN_DURATION=86400,
|
||||||
|
ALLOWED_SCOPES='openid profile launch launch/patient patient/*.read offline_access'
|
||||||
|
)
|
||||||
|
|
||||||
|
database.init_app(app)
|
||||||
|
configure_oauth(app, db=database, registered_app_model=RegisteredApp, oauth_token_model=OAuthToken, auth_code_model=AuthorizationCode)
|
||||||
|
|
||||||
|
app.register_blueprint(fhirvine_bp, url_prefix='/fhirvine')
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Update FHIRFLARE Templates
|
||||||
|
|
||||||
|
- **Add FHIRVINE Links to Navbar**:
|
||||||
|
|
||||||
|
- In `templates/base.html`, add links to FHIRVINE features:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ url_for('fhirvine.app_gallery') }}">App Gallery</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ url_for('fhirvine.test_client') }}">Test Client</a>
|
||||||
|
</li>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Run and Test
|
||||||
|
|
||||||
|
- **Install Dependencies**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Run FHIRFLARE**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
flask db upgrade
|
||||||
|
flask run --host=0.0.0.0 --port=8080
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Access FHIRVINE Features**:
|
||||||
|
|
||||||
|
- App Gallery: `http://localhost:8080/fhirvine/app-gallery`
|
||||||
|
- Test Client: `http://localhost:8080/fhirvine/test-client`
|
||||||
|
- Proxy Requests: Use `/fhirvine/oauth2/proxy/<path>` within FHIRFLARE.
|
||||||
|
|
||||||
|
## Using FHIRVINE in FHIRFLARE
|
||||||
|
|
||||||
|
- **Register Apps**: Use `/fhirvine/app-gallery` to register SMART apps within FHIRFLARE.
|
||||||
|
- **Authenticate**: Use `/fhirvine/oauth2/authorize` for OAuth2 flows.
|
||||||
|
- **Proxy FHIR Requests**: FHIRFLARE can now make FHIR requests via `/fhirvine/oauth2/proxy`, leveraging FHIRVINE’s authentication.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
- **Route Conflicts**: Ensure no overlapping routes between FHIRFLARE and FHIRVINE.
|
||||||
|
- **Database Issues**: Verify `SQLALCHEMY_DATABASE_URI` points to the same database.
|
||||||
|
- **Logs**: Check `flask run` logs for errors.
|
||||||
1
charts/.gitignore
vendored
Normal file
1
charts/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/hapi-fhir-jpaserver-0.20.0.tgz
|
||||||
1
charts/fhirflare-ig-toolkit/.gitignore
vendored
Normal file
1
charts/fhirflare-ig-toolkit/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/rendered/
|
||||||
16
charts/fhirflare-ig-toolkit/Chart.yaml
Normal file
16
charts/fhirflare-ig-toolkit/Chart.yaml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
apiVersion: v2
|
||||||
|
name: fhirflare-ig-toolkit
|
||||||
|
version: 0.5.0
|
||||||
|
description: Helm chart for deploying the fhirflare-ig-toolkit application
|
||||||
|
type: application
|
||||||
|
appVersion: "latest"
|
||||||
|
icon: https://github.com/jgsuess/FHIRFLARE-IG-Toolkit/raw/main/static/FHIRFLARE.png
|
||||||
|
keywords:
|
||||||
|
- fhir
|
||||||
|
- healthcare
|
||||||
|
- ig-toolkit
|
||||||
|
- implementation-guide
|
||||||
|
home: https://github.com/jgsuess/FHIRFLARE-IG-Toolkit
|
||||||
|
maintainers:
|
||||||
|
- name: Jörn Guy Süß
|
||||||
|
email: jgsuess@gmail.com
|
||||||
152
charts/fhirflare-ig-toolkit/templates/_helpers.tpl
Normal file
152
charts/fhirflare-ig-toolkit/templates/_helpers.tpl
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
{{/*
|
||||||
|
Expand the name of the chart.
|
||||||
|
*/}}
|
||||||
|
{{- define "fhirflare-ig-toolkit.name" -}}
|
||||||
|
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create a default fully qualified app name.
|
||||||
|
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||||
|
If release name contains chart name it will be used as a full name.
|
||||||
|
*/}}
|
||||||
|
{{- define "fhirflare-ig-toolkit.fullname" -}}
|
||||||
|
{{- if .Values.fullnameOverride }}
|
||||||
|
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- else }}
|
||||||
|
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||||
|
{{- if contains $name .Release.Name }}
|
||||||
|
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- else }}
|
||||||
|
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create chart name and version as used by the chart label.
|
||||||
|
*/}}
|
||||||
|
{{- define "fhirflare-ig-toolkit.chart" -}}
|
||||||
|
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Common labels
|
||||||
|
*/}}
|
||||||
|
{{- define "fhirflare-ig-toolkit.labels" -}}
|
||||||
|
helm.sh/chart: {{ include "fhirflare-ig-toolkit.chart" . }}
|
||||||
|
{{ include "fhirflare-ig-toolkit.selectorLabels" . }}
|
||||||
|
{{- if .Chart.AppVersion }}
|
||||||
|
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||||
|
{{- end }}
|
||||||
|
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Selector labels
|
||||||
|
*/}}
|
||||||
|
{{- define "fhirflare-ig-toolkit.selectorLabels" -}}
|
||||||
|
app.kubernetes.io/name: {{ include "fhirflare-ig-toolkit.name" . }}
|
||||||
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create the name of the service account to use
|
||||||
|
*/}}
|
||||||
|
{{- define "hapi-fhir-jpaserver.serviceAccountName" -}}
|
||||||
|
{{- if .Values.serviceAccount.create }}
|
||||||
|
{{- default (include "hapi-fhir-jpaserver.fullname" .) .Values.serviceAccount.name }}
|
||||||
|
{{- else }}
|
||||||
|
{{- default "default" .Values.serviceAccount.name }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create a default fully qualified postgresql name.
|
||||||
|
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||||
|
*/}}
|
||||||
|
{{- define "hapi-fhir-jpaserver.postgresql.fullname" -}}
|
||||||
|
{{- $name := default "postgresql" .Values.postgresql.nameOverride -}}
|
||||||
|
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Get the Postgresql credentials secret name.
|
||||||
|
*/}}
|
||||||
|
{{- define "hapi-fhir-jpaserver.postgresql.secretName" -}}
|
||||||
|
{{- if .Values.postgresql.enabled -}}
|
||||||
|
{{- if .Values.postgresql.auth.existingSecret -}}
|
||||||
|
{{- printf "%s" .Values.postgresql.auth.existingSecret -}}
|
||||||
|
{{- else -}}
|
||||||
|
{{- printf "%s" (include "hapi-fhir-jpaserver.postgresql.fullname" .) -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- else }}
|
||||||
|
{{- if .Values.externalDatabase.existingSecret -}}
|
||||||
|
{{- printf "%s" .Values.externalDatabase.existingSecret -}}
|
||||||
|
{{- else -}}
|
||||||
|
{{ printf "%s-%s" (include "hapi-fhir-jpaserver.fullname" .) "external-db" }}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Get the Postgresql credentials secret key.
|
||||||
|
*/}}
|
||||||
|
{{- define "hapi-fhir-jpaserver.postgresql.secretKey" -}}
|
||||||
|
{{- if .Values.postgresql.enabled -}}
|
||||||
|
{{- if .Values.postgresql.auth.username -}}
|
||||||
|
{{- printf "%s" .Values.postgresql.auth.secretKeys.userPasswordKey -}}
|
||||||
|
{{- else -}}
|
||||||
|
{{- printf "%s" .Values.postgresql.auth.secretKeys.adminPasswordKey -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- else }}
|
||||||
|
{{- if .Values.externalDatabase.existingSecret -}}
|
||||||
|
{{- printf "%s" .Values.externalDatabase.existingSecretKey -}}
|
||||||
|
{{- else -}}
|
||||||
|
{{- printf "postgres-password" -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Add environment variables to configure database values
|
||||||
|
*/}}
|
||||||
|
{{- define "hapi-fhir-jpaserver.database.host" -}}
|
||||||
|
{{- ternary (include "hapi-fhir-jpaserver.postgresql.fullname" .) .Values.externalDatabase.host .Values.postgresql.enabled -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Add environment variables to configure database values
|
||||||
|
*/}}
|
||||||
|
{{- define "hapi-fhir-jpaserver.database.user" -}}
|
||||||
|
{{- if .Values.postgresql.enabled -}}
|
||||||
|
{{- printf "%s" .Values.postgresql.auth.username | default "postgres" -}}
|
||||||
|
{{- else -}}
|
||||||
|
{{- printf "%s" .Values.externalDatabase.user -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Add environment variables to configure database values
|
||||||
|
*/}}
|
||||||
|
{{- define "hapi-fhir-jpaserver.database.name" -}}
|
||||||
|
{{- ternary .Values.postgresql.auth.database .Values.externalDatabase.database .Values.postgresql.enabled -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Add environment variables to configure database values
|
||||||
|
*/}}
|
||||||
|
{{- define "hapi-fhir-jpaserver.database.port" -}}
|
||||||
|
{{- ternary "5432" .Values.externalDatabase.port .Values.postgresql.enabled -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create the JDBC URL from the host, port and database name.
|
||||||
|
*/}}
|
||||||
|
{{- define "hapi-fhir-jpaserver.database.jdbcUrl" -}}
|
||||||
|
{{- $host := (include "hapi-fhir-jpaserver.database.host" .) -}}
|
||||||
|
{{- $port := (include "hapi-fhir-jpaserver.database.port" .) -}}
|
||||||
|
{{- $name := (include "hapi-fhir-jpaserver.database.name" .) -}}
|
||||||
|
{{- $appName := .Release.Name -}}
|
||||||
|
{{ printf "jdbc:postgresql://%s:%d/%s?ApplicationName=%s" $host (int $port) $name $appName }}
|
||||||
|
{{- end -}}
|
||||||
91
charts/fhirflare-ig-toolkit/templates/deployment.yaml
Normal file
91
charts/fhirflare-ig-toolkit/templates/deployment.yaml
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: {{ include "fhirflare-ig-toolkit.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "fhirflare-ig-toolkit.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
replicas: {{ .Values.replicaCount | default 1 }}
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
{{- include "fhirflare-ig-toolkit.selectorLabels" . | nindent 6 }}
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
{{- include "fhirflare-ig-toolkit.selectorLabels" . | nindent 8 }}
|
||||||
|
{{- with .Values.podAnnotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
{{- with .Values.imagePullSecrets }}
|
||||||
|
imagePullSecrets:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||||
|
containers:
|
||||||
|
- name: {{ .Chart.Name }}
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||||
|
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
|
args: ["supervisord", "-c", "/etc/supervisord.conf"]
|
||||||
|
env:
|
||||||
|
- name: APP_BASE_URL
|
||||||
|
value: {{ .Values.config.appBaseUrl | default "http://localhost:5000" | quote }}
|
||||||
|
- name: APP_MODE
|
||||||
|
value: {{ .Values.config.appMode | default "lite" | quote }}
|
||||||
|
- name: FLASK_APP
|
||||||
|
value: {{ .Values.config.flaskApp | default "app.py" | quote }}
|
||||||
|
- name: FLASK_ENV
|
||||||
|
value: {{ .Values.config.flaskEnv | default "development" | quote }}
|
||||||
|
- name: HAPI_FHIR_URL
|
||||||
|
value: {{ .Values.config.externalHapiServerUrl | default "http://external-hapi-fhir:8080/fhir" | quote }}
|
||||||
|
- name: NODE_PATH
|
||||||
|
value: {{ .Values.config.nodePath | default "/usr/lib/node_modules" | quote }}
|
||||||
|
- name: TMPDIR
|
||||||
|
value: "/tmp-dir"
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: {{ .Values.service.port | default 5000 }}
|
||||||
|
protocol: TCP
|
||||||
|
volumeMounts:
|
||||||
|
- name: logs
|
||||||
|
mountPath: /app/logs
|
||||||
|
- name: tmp-dir
|
||||||
|
mountPath: /tmp-dir
|
||||||
|
{{- with .Values.resources }}
|
||||||
|
resources:
|
||||||
|
{{- toYaml . | nindent 12 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.livenessProbe }}
|
||||||
|
livenessProbe:
|
||||||
|
{{- toYaml . | nindent 12 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.readinessProbe }}
|
||||||
|
readinessProbe:
|
||||||
|
{{- toYaml . | nindent 12 }}
|
||||||
|
{{- end }}
|
||||||
|
volumes:
|
||||||
|
- name: logs
|
||||||
|
emptyDir: {}
|
||||||
|
- name: tmp-dir
|
||||||
|
emptyDir: {}
|
||||||
|
# Always require Intel 64-bit architecture nodes
|
||||||
|
nodeSelector:
|
||||||
|
kubernetes.io/arch: amd64
|
||||||
|
{{- with .Values.nodeSelector }}
|
||||||
|
# Merge with user-defined nodeSelectors if any
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.affinity }}
|
||||||
|
affinity:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.tolerations }}
|
||||||
|
tolerations:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
36
charts/fhirflare-ig-toolkit/templates/ingress.yaml
Normal file
36
charts/fhirflare-ig-toolkit/templates/ingress.yaml
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{{- if .Values.ingress.enabled -}}
|
||||||
|
{{- $fullName := include "fhirflare-ig-toolkit.fullname" . -}}
|
||||||
|
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion }}
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion }}
|
||||||
|
apiVersion: networking.k8s.io/v1beta1
|
||||||
|
{{- else }}
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
{{- end }}
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: {{ $fullName }}
|
||||||
|
labels:
|
||||||
|
{{- include "fhirflare-ig-toolkit.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.ingress.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion }}
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: {{ $fullName }}
|
||||||
|
port:
|
||||||
|
number: {{ .Values.service.port | default 5000 }}
|
||||||
|
{{- else }}
|
||||||
|
backend:
|
||||||
|
serviceName: {{ $fullName }}
|
||||||
|
servicePort: {{ .Values.service.port | default 5000 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
18
charts/fhirflare-ig-toolkit/templates/service.yaml
Normal file
18
charts/fhirflare-ig-toolkit/templates/service.yaml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ include "fhirflare-ig-toolkit.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "fhirflare-ig-toolkit.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
type: {{ .Values.service.type | default "ClusterIP" }}
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: {{ .Values.service.port | default 5000 }}
|
||||||
|
targetPort: {{ .Values.service.port | default 5000 }}
|
||||||
|
protocol: TCP
|
||||||
|
{{- if and (eq .Values.service.type "NodePort") .Values.service.nodePort }}
|
||||||
|
nodePort: {{ .Values.service.nodePort }}
|
||||||
|
{{- end }}
|
||||||
|
selector:
|
||||||
|
{{- include "fhirflare-ig-toolkit.selectorLabels" . | nindent 4 }}
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: "{{ .Release.Name }}-fhirflare-test-endpoint"
|
||||||
|
labels:
|
||||||
|
helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
|
||||||
|
app.kubernetes.io/name: {{ .Chart.Name }}
|
||||||
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
|
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||||
|
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||||
|
app.kubernetes.io/component: tests
|
||||||
|
annotations:
|
||||||
|
"helm.sh/hook": test
|
||||||
|
spec:
|
||||||
|
restartPolicy: Never
|
||||||
|
containers:
|
||||||
|
- name: test-fhirflare-endpoint
|
||||||
|
image: curlimages/curl:8.12.1
|
||||||
|
command: ["curl", "--fail-with-body", "--retry", "5", "--retry-delay", "10"]
|
||||||
|
args: ["http://fhirflare:5000"]
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
privileged: false
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
runAsGroup: 65534
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 65534
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: 150m
|
||||||
|
ephemeral-storage: 2Gi
|
||||||
|
memory: 192Mi
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
ephemeral-storage: 50Mi
|
||||||
|
memory: 128Mi
|
||||||
89
charts/fhirflare-ig-toolkit/values.yaml
Normal file
89
charts/fhirflare-ig-toolkit/values.yaml
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
# Default values for fhirflare-ig-toolkit
|
||||||
|
replicaCount: 1
|
||||||
|
|
||||||
|
image:
|
||||||
|
repository: ghcr.io/jgsuess/fhirflare-ig-toolkit
|
||||||
|
pullPolicy: Always
|
||||||
|
tag: "latest"
|
||||||
|
|
||||||
|
imagePullSecrets: []
|
||||||
|
nameOverride: ""
|
||||||
|
fullnameOverride: ""
|
||||||
|
|
||||||
|
# FHIRflare specific configuration
|
||||||
|
config:
|
||||||
|
# Application mode: "lite" means using external HAPI server, "standalone" means running with embedded HAPI server
|
||||||
|
appMode: "lite"
|
||||||
|
# URL for the external HAPI FHIR server when in lite mode
|
||||||
|
externalHapiServerUrl: "http://external-hapi-fhir:8080/fhir"
|
||||||
|
appBaseUrl: "http://localhost:5000"
|
||||||
|
flaskApp: "app.py"
|
||||||
|
flaskEnv: "development"
|
||||||
|
nodePath: "/usr/lib/node_modules"
|
||||||
|
|
||||||
|
service:
|
||||||
|
type: ClusterIP
|
||||||
|
port: 5000
|
||||||
|
nodePort: null
|
||||||
|
|
||||||
|
podAnnotations: {}
|
||||||
|
|
||||||
|
# podSecurityContext:
|
||||||
|
# fsGroup: 65532
|
||||||
|
# fsGroupChangePolicy: OnRootMismatch
|
||||||
|
# runAsNonRoot: true
|
||||||
|
# runAsGroup: 65532
|
||||||
|
# runAsUser: 65532
|
||||||
|
# seccompProfile:
|
||||||
|
# type: RuntimeDefault
|
||||||
|
|
||||||
|
# securityContext:
|
||||||
|
# allowPrivilegeEscalation: false
|
||||||
|
# capabilities:
|
||||||
|
# drop:
|
||||||
|
# - ALL
|
||||||
|
# privileged: false
|
||||||
|
# readOnlyRootFilesystem: true
|
||||||
|
# runAsGroup: 65532
|
||||||
|
# runAsNonRoot: true
|
||||||
|
# runAsUser: 65532
|
||||||
|
# seccompProfile:
|
||||||
|
# type: RuntimeDefault
|
||||||
|
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 512Mi
|
||||||
|
ephemeral-storage: 1Gi
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 128Mi
|
||||||
|
ephemeral-storage: 100Mi
|
||||||
|
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 6
|
||||||
|
successThreshold: 1
|
||||||
|
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 6
|
||||||
|
successThreshold: 1
|
||||||
|
|
||||||
|
nodeSelector: {}
|
||||||
|
tolerations: []
|
||||||
|
affinity: {}
|
||||||
|
|
||||||
|
ingress:
|
||||||
|
# -- whether to create a primitive Ingress to expose the FHIR server HTTP endpoint
|
||||||
|
enabled: false
|
||||||
23
charts/install.sh
Executable file
23
charts/install.sh
Executable file
@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# FHIRFLARE-IG-Toolkit Installation Script
|
||||||
|
#
|
||||||
|
# Description:
|
||||||
|
# This script installs the FHIRFLARE-IG-Toolkit Helm chart into a Kubernetes cluster.
|
||||||
|
# It adds the FHIRFLARE-IG-Toolkit Helm repository and then installs the chart
|
||||||
|
# in the 'flare' namespace, creating the namespace if it doesn't exist.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./install.sh
|
||||||
|
#
|
||||||
|
# Requirements:
|
||||||
|
# - Helm (v3+)
|
||||||
|
# - kubectl configured with access to your Kubernetes cluster
|
||||||
|
#
|
||||||
|
|
||||||
|
# Add the FHIRFLARE-IG-Toolkit Helm repository
|
||||||
|
helm repo add flare https://jgsuess.github.io/FHIRFLARE-IG-Toolkit/
|
||||||
|
|
||||||
|
# Install the FHIRFLARE-IG-Toolkit chart in the 'flare' namespace
|
||||||
|
|
||||||
|
helm install flare/fhirflare-ig-toolkit --namespace flare --create-namespace --generate-name --set hapi-fhir-jpaserver.postgresql.primary.persistence.storageClass=gp2 --atomic
|
||||||
@ -3,18 +3,19 @@ services:
|
|||||||
fhirflare:
|
fhirflare:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile.lite
|
||||||
ports:
|
ports:
|
||||||
- "5000:5000"
|
- "5000:5000"
|
||||||
- "8080:8080" # Keep port exposed, even if Tomcat isn't running useful stuff in Lite
|
- "8080:8080"
|
||||||
volumes:
|
volumes:
|
||||||
- ./instance:/app/instance
|
- ./instance:/app/instance
|
||||||
- ./static/uploads:/app/static/uploads
|
- ./static/uploads:/app/static/uploads
|
||||||
- ./instance/hapi-h2-data/:/app/h2-data # Keep volume mounts consistent
|
|
||||||
- ./logs:/app/logs
|
- ./logs:/app/logs
|
||||||
environment:
|
environment:
|
||||||
- FLASK_APP=app.py
|
- FLASK_APP=app.py
|
||||||
- FLASK_ENV=development
|
- FLASK_ENV=development
|
||||||
- NODE_PATH=/usr/lib/node_modules
|
- NODE_PATH=/usr/lib/node_modules
|
||||||
- APP_MODE=lite
|
- APP_MODE=standalone
|
||||||
|
- APP_BASE_URL=http://localhost:5000
|
||||||
|
- HAPI_FHIR_URL=https://smile.sparked-fhir.com/aucore/fhir/DEFAULT/
|
||||||
command: supervisord -c /etc/supervisord.conf
|
command: supervisord -c /etc/supervisord.conf
|
||||||
|
|||||||
22
docker-compose/all-in-one/docker-compose.yml
Normal file
22
docker-compose/all-in-one/docker-compose.yml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# This docker-compose file uses ephemeral Docker named volumes for all data storage.
|
||||||
|
# These volumes persist only as long as the Docker volumes exist and are deleted if you run `docker-compose down -v`.
|
||||||
|
# No data is stored on the host filesystem. If you want persistent storage, replace these with host mounts.
|
||||||
|
services:
|
||||||
|
fhirflare-standalone:
|
||||||
|
image: ${FHIRFLARE_IMAGE:-ghcr.io/sudo-jhare/fhirflare-ig-toolkit-standalone:latest}
|
||||||
|
container_name: fhirflare-standalone
|
||||||
|
ports:
|
||||||
|
- "5000:5000"
|
||||||
|
- "8080:8080"
|
||||||
|
volumes:
|
||||||
|
- fhirflare-instance:/app/instance
|
||||||
|
- fhirflare-uploads:/app/static/uploads
|
||||||
|
- fhirflare-h2-data:/app/h2-data
|
||||||
|
- fhirflare-logs:/app/logs
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
fhirflare-instance:
|
||||||
|
fhirflare-uploads:
|
||||||
|
fhirflare-h2-data:
|
||||||
|
fhirflare-logs:
|
||||||
5
docker-compose/all-in-one/down.sh
Executable file
5
docker-compose/all-in-one/down.sh
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Stop and remove all containers defined in the Docker Compose file,
|
||||||
|
# along with any anonymous volumes attached to them.
|
||||||
|
docker compose down --volumes
|
||||||
5
docker-compose/all-in-one/up.sh
Executable file
5
docker-compose/all-in-one/up.sh
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Run Docker Compose
|
||||||
|
|
||||||
|
docker compose up --detach --force-recreate --renew-anon-volumes --always-recreate-deps
|
||||||
18
docker-compose/lite/local/application.yaml
Normal file
18
docker-compose/lite/local/application.yaml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
hapi.fhir:
|
||||||
|
ig_runtime_upload_enabled: false
|
||||||
|
narrative_enabled: true
|
||||||
|
logical_urls:
|
||||||
|
- http://terminology.hl7.org/*
|
||||||
|
- https://terminology.hl7.org/*
|
||||||
|
- http://snomed.info/*
|
||||||
|
- https://snomed.info/*
|
||||||
|
- http://unitsofmeasure.org/*
|
||||||
|
- https://unitsofmeasure.org/*
|
||||||
|
- http://loinc.org/*
|
||||||
|
- https://loinc.org/*
|
||||||
|
cors:
|
||||||
|
allow_Credentials: true
|
||||||
|
allowed_origin:
|
||||||
|
- '*'
|
||||||
|
tester.home.name: FHIRFLARE Tester
|
||||||
|
inline_resource_storage_below_size: 4000
|
||||||
50
docker-compose/lite/local/docker-compose.yml
Normal file
50
docker-compose/lite/local/docker-compose.yml
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
services:
|
||||||
|
fhirflare:
|
||||||
|
image: ${FHIRFLARE_IMAGE:-ghcr.io/sudo-jhare/fhirflare-ig-toolkit-lite:latest}
|
||||||
|
ports:
|
||||||
|
- "5000:5000"
|
||||||
|
# Ephemeral Docker named volumes for all data storage. No data is stored on the host filesystem.
|
||||||
|
volumes:
|
||||||
|
- fhirflare-instance:/app/instance
|
||||||
|
- fhirflare-uploads:/app/static/uploads
|
||||||
|
- fhirflare-h2-data:/app/h2-data
|
||||||
|
- fhirflare-logs:/app/logs
|
||||||
|
environment:
|
||||||
|
- FLASK_APP=app.py
|
||||||
|
- FLASK_ENV=development
|
||||||
|
- NODE_PATH=/usr/lib/node_modules
|
||||||
|
- APP_MODE=lite
|
||||||
|
- APP_BASE_URL=http://localhost:5000
|
||||||
|
- HAPI_FHIR_URL=http://fhir:8080/fhir
|
||||||
|
command: supervisord -c /etc/supervisord.conf
|
||||||
|
|
||||||
|
fhir:
|
||||||
|
container_name: hapi
|
||||||
|
image: "hapiproject/hapi:v8.2.0-1"
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
configs:
|
||||||
|
- source: hapi
|
||||||
|
target: /app/config/application.yaml
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: "postgres:17.2-bookworm"
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
POSTGRES_PASSWORD: admin
|
||||||
|
POSTGRES_USER: admin
|
||||||
|
POSTGRES_DB: hapi
|
||||||
|
volumes:
|
||||||
|
- ./hapi.postgress.data:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
configs:
|
||||||
|
hapi:
|
||||||
|
file: ./application.yaml
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
fhirflare-instance:
|
||||||
|
fhirflare-uploads:
|
||||||
|
fhirflare-h2-data:
|
||||||
|
fhirflare-logs:
|
||||||
5
docker-compose/lite/local/down.sh
Executable file
5
docker-compose/lite/local/down.sh
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Stop and remove all containers defined in the Docker Compose file,
|
||||||
|
# along with any anonymous volumes attached to them.
|
||||||
|
docker compose down --volumes
|
||||||
19
docker-compose/lite/local/readme.md
Normal file
19
docker-compose/lite/local/readme.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# FHIRFLARE IG Toolkit
|
||||||
|
|
||||||
|
This directory provides scripts and configuration to start and stop a FHIRFLARE instance with an attached HAPI FHIR server using Docker Compose.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
- To start the FHIRFLARE toolkit and HAPI server:
|
||||||
|
```sh
|
||||||
|
./docker-compose/up.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
- To stop and remove the containers and volumes:
|
||||||
|
```sh
|
||||||
|
./docker-compose/down.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
The web interface will be available at [http://localhost:5000](http://localhost:5000) and the HAPI FHIR server at [http://localhost:8080/fhir](http://localhost:8080/fhir).
|
||||||
|
|
||||||
|
For more details, see the configuration files in this directory.
|
||||||
5
docker-compose/lite/local/up.sh
Executable file
5
docker-compose/lite/local/up.sh
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Run Docker Compose
|
||||||
|
|
||||||
|
docker compose up --detach --force-recreate --renew-anon-volumes --always-recreate-deps
|
||||||
25
docker-compose/lite/remote/docker-compose.yml
Normal file
25
docker-compose/lite/remote/docker-compose.yml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
services:
|
||||||
|
fhirflare:
|
||||||
|
image: ${FHIRFLARE_IMAGE:-ghcr.io/sudo-jhare/fhirflare-ig-toolkit-lite:latest}
|
||||||
|
ports:
|
||||||
|
- "5000:5000"
|
||||||
|
# Ephemeral Docker named volumes for all data storage. No data is stored on the host filesystem.
|
||||||
|
volumes:
|
||||||
|
- fhirflare-instance:/app/instance
|
||||||
|
- fhirflare-uploads:/app/static/uploads
|
||||||
|
- fhirflare-h2-data:/app/h2-data
|
||||||
|
- fhirflare-logs:/app/logs
|
||||||
|
environment:
|
||||||
|
- FLASK_APP=app.py
|
||||||
|
- FLASK_ENV=development
|
||||||
|
- NODE_PATH=/usr/lib/node_modules
|
||||||
|
- APP_MODE=lite
|
||||||
|
- APP_BASE_URL=http://localhost:5000
|
||||||
|
- HAPI_FHIR_URL=https://cdr.fhirlab.net/fhir
|
||||||
|
command: supervisord -c /etc/supervisord.conf
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
fhirflare-instance:
|
||||||
|
fhirflare-uploads:
|
||||||
|
fhirflare-h2-data:
|
||||||
|
fhirflare-logs:
|
||||||
5
docker-compose/lite/remote/down.sh
Executable file
5
docker-compose/lite/remote/down.sh
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Stop and remove all containers defined in the Docker Compose file,
|
||||||
|
# along with any anonymous volumes attached to them.
|
||||||
|
docker compose down --volumes
|
||||||
19
docker-compose/lite/remote/readme.md
Normal file
19
docker-compose/lite/remote/readme.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# FHIRFLARE IG Toolkit
|
||||||
|
|
||||||
|
This directory provides scripts and configuration to start and stop a FHIRFLARE instance with an attached HAPI FHIR server using Docker Compose.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
- To start the FHIRFLARE toolkit and HAPI server:
|
||||||
|
```sh
|
||||||
|
./docker-compose/up.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
- To stop and remove the containers and volumes:
|
||||||
|
```sh
|
||||||
|
./docker-compose/down.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
The web interface will be available at [http://localhost:5000](http://localhost:5000) and the HAPI FHIR server at [http://localhost:8080/fhir](http://localhost:8080/fhir).
|
||||||
|
|
||||||
|
For more details, see the configuration files in this directory.
|
||||||
5
docker-compose/lite/remote/up.sh
Executable file
5
docker-compose/lite/remote/up.sh
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Run Docker Compose
|
||||||
|
|
||||||
|
docker compose up --detach --force-recreate --renew-anon-volumes --always-recreate-deps
|
||||||
66
docker/Dockerfile
Normal file
66
docker/Dockerfile
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Dockerfile for FHIRFLARE-IG-Toolkit (Optimized for Python/Flask)
|
||||||
|
#
|
||||||
|
# This Dockerfile builds a container for the FHIRFLARE-IG-Toolkit application.
|
||||||
|
#
|
||||||
|
# Key Features:
|
||||||
|
# - Uses python:3.11-slim as the base image for a minimal, secure Python runtime.
|
||||||
|
# - Installs Node.js and global NPM packages (gofsh, fsh-sushi) for FHIR IG tooling.
|
||||||
|
# - Sets up a Python virtual environment and installs all Python dependencies.
|
||||||
|
# - Installs and configures Supervisor to manage the Flask app and related processes.
|
||||||
|
# - Copies all necessary application code, templates, static files, and configuration.
|
||||||
|
# - Exposes ports 5000 (Flask) and 8080 (optional, for compatibility).
|
||||||
|
# - Entrypoint runs Supervisor for process management.
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# - The Dockerfile is optimized for Python. Tomcat/Java is not included.
|
||||||
|
# - Node.js is only installed if needed for FHIR IG tooling.
|
||||||
|
# - The image is suitable for development and production with minimal changes.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Optimized Dockerfile for Python (Flask)
|
||||||
|
FROM python:3.11-slim AS base
|
||||||
|
|
||||||
|
# Install system dependencies
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
curl \
|
||||||
|
coreutils \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Optional: Install Node.js if needed for GoFSH/SUSHI
|
||||||
|
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
|
||||||
|
&& apt-get install -y --no-install-recommends nodejs \
|
||||||
|
&& npm install -g gofsh fsh-sushi \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Set workdir
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy requirements and install Python dependencies
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN python -m venv /app/venv \
|
||||||
|
&& . /app/venv/bin/activate \
|
||||||
|
&& pip install --upgrade pip \
|
||||||
|
&& pip install --no-cache-dir -r requirements.txt \
|
||||||
|
&& pip uninstall -y fhirpath || true \
|
||||||
|
&& pip install --no-cache-dir fhirpathpy \
|
||||||
|
&& pip install supervisor
|
||||||
|
|
||||||
|
# Copy application files
|
||||||
|
COPY app.py .
|
||||||
|
COPY services.py .
|
||||||
|
COPY forms.py .
|
||||||
|
COPY package.py .
|
||||||
|
COPY templates/ templates/
|
||||||
|
COPY static/ static/
|
||||||
|
COPY tests/ tests/
|
||||||
|
COPY supervisord.conf /etc/supervisord.conf
|
||||||
|
|
||||||
|
# Expose ports
|
||||||
|
EXPOSE 5000 8080
|
||||||
|
|
||||||
|
# Set environment
|
||||||
|
ENV PATH="/app/venv/bin:$PATH"
|
||||||
|
|
||||||
|
# Start supervisord
|
||||||
|
CMD ["supervisord", "-c", "/etc/supervisord.conf"]
|
||||||
7
docker/build-docker.sh
Executable file
7
docker/build-docker.sh
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Build FHIRFLARE-IG-Toolkit Docker image
|
||||||
|
|
||||||
|
# Build the image using the Dockerfile in the docker directory
|
||||||
|
docker build -f Dockerfile -t fhirflare-ig-toolkit:latest ..
|
||||||
|
|
||||||
|
echo "Docker image built successfully"
|
||||||
309
forms.py
309
forms.py
@ -1,13 +1,65 @@
|
|||||||
# forms.py
|
# forms.py
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import StringField, SelectField, TextAreaField, BooleanField, SubmitField, FileField
|
from wtforms import StringField, SelectField, TextAreaField, BooleanField, SubmitField, FileField, PasswordField, SelectMultipleField
|
||||||
from wtforms.validators import DataRequired, Regexp, ValidationError, Optional
|
from wtforms.validators import DataRequired, Regexp, ValidationError, URL, Optional, InputRequired
|
||||||
|
from flask import request
|
||||||
import json
|
import json
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
import re
|
import re
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class RetrieveSplitDataForm(FlaskForm):
|
||||||
|
"""Form for retrieving FHIR bundles and splitting them into individual resources."""
|
||||||
|
fhir_server_url = StringField('FHIR Server URL', validators=[URL(), Optional()],
|
||||||
|
render_kw={'placeholder': 'e.g., https://hapi.fhir.org/baseR4'})
|
||||||
|
auth_type = SelectField('Authentication Type (for Custom URL)', choices=[
|
||||||
|
('none', 'None'),
|
||||||
|
('bearerToken', 'Bearer Token'),
|
||||||
|
('basicAuth', 'Basic Authentication')
|
||||||
|
], default='none', validators=[Optional()])
|
||||||
|
auth_token = StringField('Bearer Token', validators=[Optional()],
|
||||||
|
render_kw={'placeholder': 'Enter Bearer Token', 'type': 'password'})
|
||||||
|
basic_auth_username = StringField('Username', validators=[Optional()],
|
||||||
|
render_kw={'placeholder': 'Enter Basic Auth Username'})
|
||||||
|
basic_auth_password = PasswordField('Password', validators=[Optional()],
|
||||||
|
render_kw={'placeholder': 'Enter Basic Auth Password'})
|
||||||
|
validate_references = BooleanField('Fetch Referenced Resources', default=False,
|
||||||
|
description="If checked, fetches resources referenced by the initial bundles.")
|
||||||
|
fetch_reference_bundles = BooleanField('Fetch Full Reference Bundles (instead of individual resources)', default=False,
|
||||||
|
description="Requires 'Fetch Referenced Resources'. Fetches e.g. /Patient instead of Patient/id for each reference.",
|
||||||
|
render_kw={'data-dependency': 'validate_references'})
|
||||||
|
split_bundle_zip = FileField('Upload Bundles to Split (ZIP)', validators=[Optional()],
|
||||||
|
render_kw={'accept': '.zip'})
|
||||||
|
submit_retrieve = SubmitField('Retrieve Bundles')
|
||||||
|
submit_split = SubmitField('Split Bundles')
|
||||||
|
|
||||||
|
def validate(self, extra_validators=None):
|
||||||
|
if not super().validate(extra_validators):
|
||||||
|
return False
|
||||||
|
if self.fetch_reference_bundles.data and not self.validate_references.data:
|
||||||
|
self.fetch_reference_bundles.errors.append('Cannot fetch full reference bundles unless "Fetch Referenced Resources" is also checked.')
|
||||||
|
return False
|
||||||
|
if self.auth_type.data == 'bearerToken' and self.submit_retrieve.data and not self.auth_token.data:
|
||||||
|
self.auth_token.errors.append('Bearer Token is required when Bearer Token authentication is selected.')
|
||||||
|
return False
|
||||||
|
if self.auth_type.data == 'basicAuth' and self.submit_retrieve.data:
|
||||||
|
if not self.basic_auth_username.data:
|
||||||
|
self.basic_auth_username.errors.append('Username is required for Basic Authentication.')
|
||||||
|
return False
|
||||||
|
if not self.basic_auth_password.data:
|
||||||
|
self.basic_auth_password.errors.append('Password is required for Basic Authentication.')
|
||||||
|
return False
|
||||||
|
if self.split_bundle_zip.data:
|
||||||
|
if not self.split_bundle_zip.data.filename.lower().endswith('.zip'):
|
||||||
|
self.split_bundle_zip.errors.append('File must be a ZIP file.')
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
# Existing forms (IgImportForm, ValidationForm) remain unchanged
|
|
||||||
class IgImportForm(FlaskForm):
|
class IgImportForm(FlaskForm):
|
||||||
|
"""Form for importing Implementation Guides."""
|
||||||
package_name = StringField('Package Name', validators=[
|
package_name = StringField('Package Name', validators=[
|
||||||
DataRequired(),
|
DataRequired(),
|
||||||
Regexp(r'^[a-zA-Z0-9][a-zA-Z0-9\-\.]*[a-zA-Z0-9]$', message="Invalid package name format.")
|
Regexp(r'^[a-zA-Z0-9][a-zA-Z0-9\-\.]*[a-zA-Z0-9]$', message="Invalid package name format.")
|
||||||
@ -23,7 +75,64 @@ class IgImportForm(FlaskForm):
|
|||||||
], default='recursive')
|
], default='recursive')
|
||||||
submit = SubmitField('Import')
|
submit = SubmitField('Import')
|
||||||
|
|
||||||
|
class ManualIgImportForm(FlaskForm):
|
||||||
|
"""Form for manual importing Implementation Guides via file or URL."""
|
||||||
|
import_mode = SelectField('Import Mode', choices=[
|
||||||
|
('file', 'Upload File'),
|
||||||
|
('url', 'From URL')
|
||||||
|
], default='file', validators=[DataRequired()])
|
||||||
|
tgz_file = FileField('IG Package File (.tgz)', validators=[Optional()],
|
||||||
|
render_kw={'accept': '.tgz'})
|
||||||
|
tgz_url = StringField('IG Package URL', validators=[Optional(), URL()],
|
||||||
|
render_kw={'placeholder': 'e.g., https://example.com/hl7.fhir.au.core-1.1.0-preview.tgz'})
|
||||||
|
dependency_mode = SelectField('Dependency Mode', choices=[
|
||||||
|
('recursive', 'Current Recursive'),
|
||||||
|
('patch-canonical', 'Patch Canonical Versions'),
|
||||||
|
('tree-shaking', 'Tree Shaking (Only Used Dependencies)')
|
||||||
|
], default='recursive')
|
||||||
|
resolve_dependencies = BooleanField('Resolve Dependencies', default=True,
|
||||||
|
render_kw={'class': 'form-check-input'})
|
||||||
|
submit = SubmitField('Import')
|
||||||
|
|
||||||
|
def validate(self, extra_validators=None):
|
||||||
|
if not super().validate(extra_validators):
|
||||||
|
return False
|
||||||
|
mode = self.import_mode.data
|
||||||
|
has_file = request and request.files and self.tgz_file.name in request.files and request.files[self.tgz_file.name].filename != ''
|
||||||
|
has_url = bool(self.tgz_url.data) # Convert to boolean: True if non-empty string
|
||||||
|
|
||||||
|
# Ensure exactly one input method is used
|
||||||
|
inputs_provided = sum([has_file, has_url])
|
||||||
|
if inputs_provided != 1:
|
||||||
|
if inputs_provided == 0:
|
||||||
|
self.import_mode.errors.append('Please provide input for one import method (File or URL).')
|
||||||
|
else:
|
||||||
|
self.import_mode.errors.append('Please use only one import method at a time.')
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Validate based on import mode
|
||||||
|
if mode == 'file':
|
||||||
|
if not has_file:
|
||||||
|
self.tgz_file.errors.append('A .tgz file is required for File import.')
|
||||||
|
return False
|
||||||
|
if not self.tgz_file.data.filename.lower().endswith('.tgz'):
|
||||||
|
self.tgz_file.errors.append('File must be a .tgz file.')
|
||||||
|
return False
|
||||||
|
elif mode == 'url':
|
||||||
|
if not has_url:
|
||||||
|
self.tgz_url.errors.append('A valid URL is required for URL import.')
|
||||||
|
return False
|
||||||
|
if not self.tgz_url.data.lower().endswith('.tgz'):
|
||||||
|
self.tgz_url.errors.append('URL must point to a .tgz file.')
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
self.import_mode.errors.append('Invalid import mode selected.')
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
class ValidationForm(FlaskForm):
|
class ValidationForm(FlaskForm):
|
||||||
|
"""Form for validating FHIR samples."""
|
||||||
package_name = StringField('Package Name', validators=[DataRequired()])
|
package_name = StringField('Package Name', validators=[DataRequired()])
|
||||||
version = StringField('Package Version', validators=[DataRequired()])
|
version = StringField('Package Version', validators=[DataRequired()])
|
||||||
include_dependencies = BooleanField('Include Dependencies', default=True)
|
include_dependencies = BooleanField('Include Dependencies', default=True)
|
||||||
@ -33,24 +142,11 @@ class ValidationForm(FlaskForm):
|
|||||||
], default='single')
|
], default='single')
|
||||||
sample_input = TextAreaField('Sample Input', validators=[
|
sample_input = TextAreaField('Sample Input', validators=[
|
||||||
DataRequired(),
|
DataRequired(),
|
||||||
lambda form, field: validate_json(field.data, form.mode.data)
|
|
||||||
])
|
])
|
||||||
submit = SubmitField('Validate')
|
submit = SubmitField('Validate')
|
||||||
|
|
||||||
def validate_json(data, mode):
|
|
||||||
"""Custom validator to ensure input is valid JSON and matches the selected mode."""
|
|
||||||
try:
|
|
||||||
parsed = json.loads(data)
|
|
||||||
if mode == 'single' and not isinstance(parsed, dict):
|
|
||||||
raise ValueError("Single resource mode requires a JSON object.")
|
|
||||||
if mode == 'bundle' and (not isinstance(parsed, dict) or parsed.get('resourceType') != 'Bundle'):
|
|
||||||
raise ValueError("Bundle mode requires a JSON object with resourceType 'Bundle'.")
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
raise ValidationError("Invalid JSON format.")
|
|
||||||
except ValueError as e:
|
|
||||||
raise ValidationError(str(e))
|
|
||||||
|
|
||||||
class FSHConverterForm(FlaskForm):
|
class FSHConverterForm(FlaskForm):
|
||||||
|
"""Form for converting FHIR resources to FSH."""
|
||||||
package = SelectField('FHIR Package (Optional)', choices=[('', 'None')], validators=[Optional()])
|
package = SelectField('FHIR Package (Optional)', choices=[('', 'None')], validators=[Optional()])
|
||||||
input_mode = SelectField('Input Mode', choices=[
|
input_mode = SelectField('Input Mode', choices=[
|
||||||
('file', 'Upload File'),
|
('file', 'Upload File'),
|
||||||
@ -70,7 +166,7 @@ class FSHConverterForm(FlaskForm):
|
|||||||
('info', 'Info'),
|
('info', 'Info'),
|
||||||
('debug', 'Debug')
|
('debug', 'Debug')
|
||||||
], validators=[DataRequired()])
|
], validators=[DataRequired()])
|
||||||
fhir_version = SelectField('FXML Version', choices=[
|
fhir_version = SelectField('FHIR Version', choices=[
|
||||||
('', 'Auto-detect'),
|
('', 'Auto-detect'),
|
||||||
('4.0.1', 'R4'),
|
('4.0.1', 'R4'),
|
||||||
('4.3.0', 'R4B'),
|
('4.3.0', 'R4B'),
|
||||||
@ -91,19 +187,20 @@ class FSHConverterForm(FlaskForm):
|
|||||||
def validate(self, extra_validators=None):
|
def validate(self, extra_validators=None):
|
||||||
if not super().validate(extra_validators):
|
if not super().validate(extra_validators):
|
||||||
return False
|
return False
|
||||||
if self.input_mode.data == 'file' and not self.fhir_file.data:
|
has_file_in_request = request and request.files and self.fhir_file.name in request.files and request.files[self.fhir_file.name].filename != ''
|
||||||
self.fhir_file.errors.append('File is required when input mode is Upload File.')
|
if self.input_mode.data == 'file' and not has_file_in_request:
|
||||||
return False
|
if not self.fhir_file.data:
|
||||||
|
self.fhir_file.errors.append('File is required when input mode is Upload File.')
|
||||||
|
return False
|
||||||
if self.input_mode.data == 'text' and not self.fhir_text.data:
|
if self.input_mode.data == 'text' and not self.fhir_text.data:
|
||||||
self.fhir_text.errors.append('Text input is required when input mode is Paste Text.')
|
self.fhir_text.errors.append('Text input is required when input mode is Paste Text.')
|
||||||
return False
|
return False
|
||||||
if self.input_mode.data == 'text' and self.fhir_text.data:
|
if self.input_mode.data == 'text' and self.fhir_text.data:
|
||||||
try:
|
try:
|
||||||
content = self.fhir_text.data.strip()
|
content = self.fhir_text.data.strip()
|
||||||
if content.startswith('{'):
|
if not content: pass
|
||||||
json.loads(content)
|
elif content.startswith('{'): json.loads(content)
|
||||||
elif content.startswith('<'):
|
elif content.startswith('<'): ET.fromstring(content)
|
||||||
ET.fromstring(content)
|
|
||||||
else:
|
else:
|
||||||
self.fhir_text.errors.append('Text input must be valid JSON or XML.')
|
self.fhir_text.errors.append('Text input must be valid JSON or XML.')
|
||||||
return False
|
return False
|
||||||
@ -111,14 +208,162 @@ class FSHConverterForm(FlaskForm):
|
|||||||
self.fhir_text.errors.append('Invalid JSON or XML format.')
|
self.fhir_text.errors.append('Invalid JSON or XML format.')
|
||||||
return False
|
return False
|
||||||
if self.dependencies.data:
|
if self.dependencies.data:
|
||||||
for dep in self.dependencies.data.split('\n'):
|
for dep in self.dependencies.data.splitlines():
|
||||||
dep = dep.strip()
|
dep = dep.strip()
|
||||||
if dep and not re.match(r'^[a-zA-Z0-9\-\.]+@[a-zA-Z0-9\.\-]+$', dep):
|
if dep and not re.match(r'^[a-zA-Z0-9\-\.]+@[a-zA-Z0-9\.\-]+$', dep):
|
||||||
self.dependencies.errors.append(f'Invalid dependency format: {dep}. Use package@version (e.g., hl7.fhir.us.core@6.1.0).')
|
self.dependencies.errors.append(f'Invalid dependency format: "{dep}". Use package@version (e.g., hl7.fhir.us.core@6.1.0).')
|
||||||
return False
|
return False
|
||||||
if self.alias_file.data:
|
has_alias_file_in_request = request and request.files and self.alias_file.name in request.files and request.files[self.alias_file.name].filename != ''
|
||||||
content = self.alias_file.data.read().decode('utf-8')
|
alias_file_data = self.alias_file.data or (request.files.get(self.alias_file.name) if request else None)
|
||||||
if not content.strip().endswith('.fsh'):
|
if alias_file_data and alias_file_data.filename:
|
||||||
self.alias_file.errors.append('Alias file must be a valid FSH file (.fsh).')
|
if not alias_file_data.filename.lower().endswith('.fsh'):
|
||||||
|
self.alias_file.errors.append('Alias file should have a .fsh extension.')
|
||||||
|
return True
|
||||||
|
|
||||||
|
class TestDataUploadForm(FlaskForm):
|
||||||
|
"""Form for uploading FHIR test data."""
|
||||||
|
fhir_server_url = StringField('Target FHIR Server URL', validators=[DataRequired(), URL()],
|
||||||
|
render_kw={'placeholder': 'e.g., http://localhost:8080/fhir'})
|
||||||
|
auth_type = SelectField('Authentication Type', choices=[
|
||||||
|
('none', 'None'),
|
||||||
|
('bearerToken', 'Bearer Token'),
|
||||||
|
('basic', 'Basic Authentication')
|
||||||
|
], default='none')
|
||||||
|
auth_token = StringField('Bearer Token', validators=[Optional()],
|
||||||
|
render_kw={'placeholder': 'Enter Bearer Token', 'type': 'password'})
|
||||||
|
username = StringField('Username', validators=[Optional()],
|
||||||
|
render_kw={'placeholder': 'Enter Basic Auth Username'})
|
||||||
|
password = PasswordField('Password', validators=[Optional()],
|
||||||
|
render_kw={'placeholder': 'Enter Basic Auth Password'})
|
||||||
|
test_data_file = FileField('Select Test Data File(s)', validators=[InputRequired("Please select at least one file.")],
|
||||||
|
render_kw={'multiple': True, 'accept': '.json,.xml,.zip'})
|
||||||
|
validate_before_upload = BooleanField('Validate Resources Before Upload?', default=False,
|
||||||
|
description="Validate resources against selected package profile before uploading.")
|
||||||
|
validation_package_id = SelectField('Validation Profile Package (Optional)',
|
||||||
|
choices=[('', '-- Select Package for Validation --')],
|
||||||
|
validators=[Optional()],
|
||||||
|
description="Select the processed IG package to use for validation.")
|
||||||
|
upload_mode = SelectField('Upload Mode', choices=[
|
||||||
|
('individual', 'Individual Resources'),
|
||||||
|
('transaction', 'Transaction Bundle')
|
||||||
|
], default='individual')
|
||||||
|
use_conditional_uploads = BooleanField('Use Conditional Upload (Individual Mode Only)?', default=True,
|
||||||
|
description="If checked, checks resource existence (GET) and uses If-Match (PUT) or creates (PUT). If unchecked, uses simple PUT for all.")
|
||||||
|
error_handling = SelectField('Error Handling', choices=[
|
||||||
|
('stop', 'Stop on First Error'),
|
||||||
|
('continue', 'Continue on Error')
|
||||||
|
], default='stop')
|
||||||
|
submit = SubmitField('Upload and Process')
|
||||||
|
|
||||||
|
def validate(self, extra_validators=None):
|
||||||
|
if not super().validate(extra_validators):
|
||||||
|
return False
|
||||||
|
if self.validate_before_upload.data and not self.validation_package_id.data:
|
||||||
|
self.validation_package_id.errors.append('Please select a package to validate against when pre-upload validation is enabled.')
|
||||||
|
return False
|
||||||
|
if self.use_conditional_uploads.data and self.upload_mode.data == 'transaction':
|
||||||
|
self.use_conditional_uploads.errors.append('Conditional Uploads only apply to the "Individual Resources" mode.')
|
||||||
|
return False
|
||||||
|
if self.auth_type.data == 'bearerToken' and not self.auth_token.data:
|
||||||
|
self.auth_token.errors.append('Bearer Token is required when Bearer Token authentication is selected.')
|
||||||
|
return False
|
||||||
|
if self.auth_type.data == 'basic':
|
||||||
|
if not self.username.data:
|
||||||
|
self.username.errors.append('Username is required for Basic Authentication.')
|
||||||
return False
|
return False
|
||||||
return True
|
if not self.password.data:
|
||||||
|
self.password.errors.append('Password is required for Basic Authentication.')
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
class FhirRequestForm(FlaskForm):
|
||||||
|
fhir_server_url = StringField('FHIR Server URL', validators=[URL(), Optional()],
|
||||||
|
render_kw={'placeholder': 'e.g., https://hapi.fhir.org/baseR4'})
|
||||||
|
auth_type = SelectField('Authentication Type (for Custom URL)', choices=[
|
||||||
|
('none', 'None'),
|
||||||
|
('bearerToken', 'Bearer Token'),
|
||||||
|
('basicAuth', 'Basic Authentication')
|
||||||
|
], default='none', validators=[Optional()])
|
||||||
|
auth_token = StringField('Bearer Token', validators=[Optional()],
|
||||||
|
render_kw={'placeholder': 'Enter Bearer Token', 'type': 'password'})
|
||||||
|
basic_auth_username = StringField('Username', validators=[Optional()],
|
||||||
|
render_kw={'placeholder': 'Enter Basic Auth Username'})
|
||||||
|
basic_auth_password = PasswordField('Password', validators=[Optional()],
|
||||||
|
render_kw={'placeholder': 'Enter Basic Auth Password'})
|
||||||
|
submit = SubmitField('Send Request')
|
||||||
|
|
||||||
|
def validate(self, extra_validators=None):
|
||||||
|
if not super().validate(extra_validators):
|
||||||
|
return False
|
||||||
|
if self.fhir_server_url.data:
|
||||||
|
if self.auth_type.data == 'bearerToken' and not self.auth_token.data:
|
||||||
|
self.auth_token.errors.append('Bearer Token is required when Bearer Token authentication is selected for a custom URL.')
|
||||||
|
return False
|
||||||
|
if self.auth_type.data == 'basicAuth':
|
||||||
|
if not self.basic_auth_username.data:
|
||||||
|
self.basic_auth_username.errors.append('Username is required for Basic Authentication with a custom URL.')
|
||||||
|
return False
|
||||||
|
if not self.basic_auth_password.data:
|
||||||
|
self.basic_auth_password.errors.append('Password is required for Basic Authentication with a custom URL.')
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
class IgYamlForm(FlaskForm):
|
||||||
|
"""Form to select IGs for YAML generation."""
|
||||||
|
igs = SelectMultipleField('Select IGs to include', validators=[DataRequired()])
|
||||||
|
generate_yaml = SubmitField('Generate YAML')
|
||||||
|
|
||||||
|
# --- New ValidationForm class for the new page ---
|
||||||
|
class ValidationForm(FlaskForm):
|
||||||
|
"""Form for validating a single FHIR resource with various options."""
|
||||||
|
# Added fields to match the HTML template
|
||||||
|
package_name = StringField('Package Name', validators=[Optional()])
|
||||||
|
version = StringField('Package Version', validators=[Optional()])
|
||||||
|
|
||||||
|
# Main content fields
|
||||||
|
fhir_resource = TextAreaField('FHIR Resource (JSON or XML)', validators=[InputRequired()],
|
||||||
|
render_kw={'placeholder': 'Paste your FHIR JSON here...'})
|
||||||
|
fhir_version = SelectField('FHIR Version', choices=[
|
||||||
|
('4.0.1', 'R4 (4.0.1)'),
|
||||||
|
('3.0.2', 'STU3 (3.0.2)'),
|
||||||
|
('5.0.0', 'R5 (5.0.0)')
|
||||||
|
], default='4.0.1', validators=[DataRequired()])
|
||||||
|
|
||||||
|
# Flags and options from validator settings.pdf
|
||||||
|
do_native = BooleanField('Native Validation (doNative)')
|
||||||
|
hint_about_must_support = BooleanField('Must Support (hintAboutMustSupport)')
|
||||||
|
assume_valid_rest_references = BooleanField('Assume Valid Rest References (assumeValidRestReferences)')
|
||||||
|
no_extensible_binding_warnings = BooleanField('Extensible Binding Warnings (noExtensibleBindingWarnings)')
|
||||||
|
show_times = BooleanField('Show Times (-show-times)')
|
||||||
|
allow_example_urls = BooleanField('Allow Example URLs (-allow-example-urls)')
|
||||||
|
check_ips_codes = BooleanField('Check IPS Codes (-check-ips-codes)')
|
||||||
|
allow_any_extensions = BooleanField('Allow Any Extensions (-allow-any-extensions)')
|
||||||
|
tx_routing = BooleanField('Show Terminology Routing (-tx-routing)')
|
||||||
|
|
||||||
|
# SNOMED CT options
|
||||||
|
snomed_ct_version = SelectField('Select SNOMED Version', choices=[
|
||||||
|
('', '-- No selection --'),
|
||||||
|
('intl', 'International edition (900000000000207008)'),
|
||||||
|
('us', 'US edition (731000124108)'),
|
||||||
|
('uk', 'United Kingdom Edition (999000041000000102)'),
|
||||||
|
('es', 'Spanish Language Edition (449081005)'),
|
||||||
|
('nl', 'Netherlands Edition (11000146104)'),
|
||||||
|
('ca', 'Canadian Edition (20611000087101)'),
|
||||||
|
('dk', 'Danish Edition (554471000005108)'),
|
||||||
|
('se', 'Swedish Edition (45991000052106)'),
|
||||||
|
('au', 'Australian Edition (32506021000036107)'),
|
||||||
|
('be', 'Belgium Edition (11000172109)')
|
||||||
|
], validators=[Optional()])
|
||||||
|
|
||||||
|
# Text input fields
|
||||||
|
profiles = StringField('Profiles',
|
||||||
|
validators=[Optional()],
|
||||||
|
render_kw={'placeholder': 'e.g. http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient'})
|
||||||
|
extensions = StringField('Extensions',
|
||||||
|
validators=[Optional()],
|
||||||
|
render_kw={'placeholder': 'e.g. http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace'})
|
||||||
|
terminology_server = StringField('Terminology Server',
|
||||||
|
validators=[Optional()],
|
||||||
|
render_kw={'placeholder': 'e.g. http://tx.fhir.org'})
|
||||||
|
|
||||||
|
submit = SubmitField('Validate')
|
||||||
@ -1 +0,0 @@
|
|||||||
|
|
||||||
@ -1 +0,0 @@
|
|||||||
|
|
||||||
Binary file not shown.
Binary file not shown.
@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"package_name": "hl7.fhir.au.base",
|
|
||||||
"version": "5.1.0-preview",
|
|
||||||
"dependency_mode": "recursive",
|
|
||||||
"imported_dependencies": [
|
|
||||||
{
|
|
||||||
"name": "hl7.fhir.r4.core",
|
|
||||||
"version": "4.0.1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "hl7.terminology.r4",
|
|
||||||
"version": "6.2.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "hl7.fhir.uv.extensions.r4",
|
|
||||||
"version": "5.2.0"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"complies_with_profiles": [],
|
|
||||||
"imposed_profiles": [],
|
|
||||||
"timestamp": "2025-04-17T04:04:45.070781+00:00"
|
|
||||||
}
|
|
||||||
Binary file not shown.
@ -1,34 +0,0 @@
|
|||||||
{
|
|
||||||
"package_name": "hl7.fhir.au.core",
|
|
||||||
"version": "1.1.0-preview",
|
|
||||||
"dependency_mode": "recursive",
|
|
||||||
"imported_dependencies": [
|
|
||||||
{
|
|
||||||
"name": "hl7.fhir.r4.core",
|
|
||||||
"version": "4.0.1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "hl7.terminology.r4",
|
|
||||||
"version": "6.2.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "hl7.fhir.uv.extensions.r4",
|
|
||||||
"version": "5.2.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "hl7.fhir.au.base",
|
|
||||||
"version": "5.1.0-preview"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "hl7.fhir.uv.smart-app-launch",
|
|
||||||
"version": "2.1.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "hl7.fhir.uv.ipa",
|
|
||||||
"version": "1.0.0"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"complies_with_profiles": [],
|
|
||||||
"imposed_profiles": [],
|
|
||||||
"timestamp": "2025-04-17T04:04:20.523471+00:00"
|
|
||||||
}
|
|
||||||
Binary file not shown.
@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"package_name": "hl7.fhir.r4.core",
|
|
||||||
"version": "4.0.1",
|
|
||||||
"dependency_mode": "recursive",
|
|
||||||
"imported_dependencies": [],
|
|
||||||
"complies_with_profiles": [],
|
|
||||||
"imposed_profiles": [],
|
|
||||||
"timestamp": "2025-04-17T04:04:29.230227+00:00"
|
|
||||||
}
|
|
||||||
Binary file not shown.
@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"package_name": "hl7.fhir.uv.extensions.r4",
|
|
||||||
"version": "5.2.0",
|
|
||||||
"dependency_mode": "recursive",
|
|
||||||
"imported_dependencies": [
|
|
||||||
{
|
|
||||||
"name": "hl7.fhir.r4.core",
|
|
||||||
"version": "4.0.1"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"complies_with_profiles": [],
|
|
||||||
"imposed_profiles": [],
|
|
||||||
"timestamp": "2025-04-17T04:04:41.588025+00:00"
|
|
||||||
}
|
|
||||||
Binary file not shown.
@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"package_name": "hl7.fhir.uv.ipa",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"dependency_mode": "recursive",
|
|
||||||
"imported_dependencies": [
|
|
||||||
{
|
|
||||||
"name": "hl7.fhir.r4.core",
|
|
||||||
"version": "4.0.1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "hl7.terminology.r4",
|
|
||||||
"version": "5.0.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "hl7.fhir.uv.smart-app-launch",
|
|
||||||
"version": "2.0.0"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"complies_with_profiles": [],
|
|
||||||
"imposed_profiles": [],
|
|
||||||
"timestamp": "2025-04-17T04:04:49.395594+00:00"
|
|
||||||
}
|
|
||||||
Binary file not shown.
@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"package_name": "hl7.fhir.uv.smart-app-launch",
|
|
||||||
"version": "2.0.0",
|
|
||||||
"dependency_mode": "recursive",
|
|
||||||
"imported_dependencies": [
|
|
||||||
{
|
|
||||||
"name": "hl7.fhir.r4.core",
|
|
||||||
"version": "4.0.1"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"complies_with_profiles": [],
|
|
||||||
"imposed_profiles": [],
|
|
||||||
"timestamp": "2025-04-17T04:04:56.492512+00:00"
|
|
||||||
}
|
|
||||||
Binary file not shown.
@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"package_name": "hl7.fhir.uv.smart-app-launch",
|
|
||||||
"version": "2.1.0",
|
|
||||||
"dependency_mode": "recursive",
|
|
||||||
"imported_dependencies": [
|
|
||||||
{
|
|
||||||
"name": "hl7.fhir.r4.core",
|
|
||||||
"version": "4.0.1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "hl7.terminology.r4",
|
|
||||||
"version": "5.0.0"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"complies_with_profiles": [],
|
|
||||||
"imposed_profiles": [],
|
|
||||||
"timestamp": "2025-04-17T04:04:46.943079+00:00"
|
|
||||||
}
|
|
||||||
Binary file not shown.
@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"package_name": "hl7.terminology.r4",
|
|
||||||
"version": "5.0.0",
|
|
||||||
"dependency_mode": "recursive",
|
|
||||||
"imported_dependencies": [
|
|
||||||
{
|
|
||||||
"name": "hl7.fhir.r4.core",
|
|
||||||
"version": "4.0.1"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"complies_with_profiles": [],
|
|
||||||
"imposed_profiles": [],
|
|
||||||
"timestamp": "2025-04-17T04:04:54.857273+00:00"
|
|
||||||
}
|
|
||||||
Binary file not shown.
@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"package_name": "hl7.terminology.r4",
|
|
||||||
"version": "6.2.0",
|
|
||||||
"dependency_mode": "recursive",
|
|
||||||
"imported_dependencies": [
|
|
||||||
{
|
|
||||||
"name": "hl7.fhir.r4.core",
|
|
||||||
"version": "4.0.1"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"complies_with_profiles": [],
|
|
||||||
"imposed_profiles": [],
|
|
||||||
"timestamp": "2025-04-17T04:04:37.703082+00:00"
|
|
||||||
}
|
|
||||||
Binary file not shown.
@ -1,6 +0,0 @@
|
|||||||
#FileLock
|
|
||||||
#Mon Apr 21 13:28:52 UTC 2025
|
|
||||||
server=172.18.0.2\:37033
|
|
||||||
hostName=d3691f0dbfcf
|
|
||||||
method=file
|
|
||||||
id=196588987143154de87600de82a07a5280026b49718
|
|
||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -1,8 +0,0 @@
|
|||||||
* Serving Flask app 'app'
|
|
||||||
* Debug mode: off
|
|
||||||
* Serving Flask app 'app'
|
|
||||||
* Debug mode: off
|
|
||||||
* Serving Flask app 'app'
|
|
||||||
* Debug mode: off
|
|
||||||
* Serving Flask app 'app'
|
|
||||||
* Debug mode: off
|
|
||||||
@ -1,311 +0,0 @@
|
|||||||
INFO:__main__:Application running in mode: lite
|
|
||||||
DEBUG:__main__:Instance path configuration: /app/instance
|
|
||||||
DEBUG:__main__:Database URI: sqlite:////app/instance/fhir_ig.db
|
|
||||||
DEBUG:__main__:Packages path: /app/instance/fhir_packages
|
|
||||||
DEBUG:__main__:Flask instance folder path: /app/instance
|
|
||||||
DEBUG:__main__:Directories created/verified: Instance: /app/instance, Packages: /app/instance/fhir_packages
|
|
||||||
DEBUG:__main__:Attempting to create database tables for URI: sqlite:////app/instance/fhir_ig.db
|
|
||||||
INFO:__main__:Database tables created successfully (if they didn't exist).
|
|
||||||
DEBUG:__main__:Instance path configuration: /app/instance
|
|
||||||
DEBUG:__main__:Database URI: sqlite:////app/instance/fhir_ig.db
|
|
||||||
DEBUG:__main__:Packages path: /app/instance/fhir_packages
|
|
||||||
DEBUG:__main__:Flask instance folder path: /app/instance
|
|
||||||
DEBUG:__main__:Directories created/verified: Instance: /app/instance, Packages: /app/instance/fhir_packages
|
|
||||||
DEBUG:__main__:Attempting to create database tables for URI: sqlite:////app/instance/fhir_ig.db
|
|
||||||
INFO:__main__:Database tables created successfully (if they didn't exist).
|
|
||||||
INFO:werkzeug:[31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
|
||||||
* Running on all addresses (0.0.0.0)
|
|
||||||
* Running on http://127.0.0.1:5000
|
|
||||||
* Running on http://172.18.0.2:5000
|
|
||||||
INFO:werkzeug:[33mPress CTRL+C to quit[0m
|
|
||||||
INFO:__main__:Application running in mode: lite
|
|
||||||
DEBUG:__main__:Instance path configuration: /app/instance
|
|
||||||
DEBUG:__main__:Database URI: sqlite:////app/instance/fhir_ig.db
|
|
||||||
DEBUG:__main__:Packages path: /app/instance/fhir_packages
|
|
||||||
DEBUG:__main__:Flask instance folder path: /app/instance
|
|
||||||
DEBUG:__main__:Directories created/verified: Instance: /app/instance, Packages: /app/instance/fhir_packages
|
|
||||||
DEBUG:__main__:Attempting to create database tables for URI: sqlite:////app/instance/fhir_ig.db
|
|
||||||
INFO:__main__:Database tables created successfully (if they didn't exist).
|
|
||||||
DEBUG:__main__:Instance path configuration: /app/instance
|
|
||||||
DEBUG:__main__:Database URI: sqlite:////app/instance/fhir_ig.db
|
|
||||||
DEBUG:__main__:Packages path: /app/instance/fhir_packages
|
|
||||||
DEBUG:__main__:Flask instance folder path: /app/instance
|
|
||||||
DEBUG:__main__:Directories created/verified: Instance: /app/instance, Packages: /app/instance/fhir_packages
|
|
||||||
DEBUG:__main__:Attempting to create database tables for URI: sqlite:////app/instance/fhir_ig.db
|
|
||||||
INFO:__main__:Database tables created successfully (if they didn't exist).
|
|
||||||
INFO:werkzeug:[31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
|
||||||
* Running on all addresses (0.0.0.0)
|
|
||||||
* Running on http://127.0.0.1:5000
|
|
||||||
* Running on http://172.18.0.2:5000
|
|
||||||
INFO:werkzeug:[33mPress CTRL+C to quit[0m
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [21/Apr/2025 23:36:15] "GET / HTTP/1.1" 200 -
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [21/Apr/2025 23:36:15] "GET /static/FHIRFLARE.png HTTP/1.1" 200 -
|
|
||||||
DEBUG:__main__:Scanning packages directory: /app/instance/fhir_packages
|
|
||||||
DEBUG:services:Parsed 'hl7.fhir.au.base-5.1.0-preview.tgz' -> name='hl7.fhir.au.base-5.1.0', version='preview'
|
|
||||||
DEBUG:services:Parsed 'hl7.fhir.au.core-1.1.0-preview.tgz' -> name='hl7.fhir.au.core-1.1.0', version='preview'
|
|
||||||
DEBUG:services:Parsed 'hl7.fhir.r4.core-4.0.1.tgz' -> name='hl7.fhir.r4.core', version='4.0.1'
|
|
||||||
DEBUG:services:Parsed 'hl7.fhir.uv.extensions.r4-5.2.0.tgz' -> name='hl7.fhir.uv.extensions.r4', version='5.2.0'
|
|
||||||
DEBUG:services:Parsed 'hl7.fhir.uv.ipa-1.0.0.tgz' -> name='hl7.fhir.uv.ipa', version='1.0.0'
|
|
||||||
DEBUG:services:Parsed 'hl7.fhir.uv.smart-app-launch-2.0.0.tgz' -> name='hl7.fhir.uv.smart-app-launch', version='2.0.0'
|
|
||||||
DEBUG:services:Parsed 'hl7.fhir.uv.smart-app-launch-2.1.0.tgz' -> name='hl7.fhir.uv.smart-app-launch', version='2.1.0'
|
|
||||||
DEBUG:services:Parsed 'hl7.terminology.r4-5.0.0.tgz' -> name='hl7.terminology.r4', version='5.0.0'
|
|
||||||
DEBUG:services:Parsed 'hl7.terminology.r4-6.2.0.tgz' -> name='hl7.terminology.r4', version='6.2.0'
|
|
||||||
DEBUG:__main__:Found packages: [{'name': 'hl7.fhir.au.base', 'version': '5.1.0-preview', 'filename': 'hl7.fhir.au.base-5.1.0-preview.tgz'}, {'name': 'hl7.fhir.au.core', 'version': '1.1.0-preview', 'filename': 'hl7.fhir.au.core-1.1.0-preview.tgz'}, {'name': 'hl7.fhir.r4.core', 'version': '4.0.1', 'filename': 'hl7.fhir.r4.core-4.0.1.tgz'}, {'name': 'hl7.fhir.uv.extensions.r4', 'version': '5.2.0', 'filename': 'hl7.fhir.uv.extensions.r4-5.2.0.tgz'}, {'name': 'hl7.fhir.uv.ipa', 'version': '1.0.0', 'filename': 'hl7.fhir.uv.ipa-1.0.0.tgz'}, {'name': 'hl7.fhir.uv.smart-app-launch', 'version': '2.0.0', 'filename': 'hl7.fhir.uv.smart-app-launch-2.0.0.tgz'}, {'name': 'hl7.fhir.uv.smart-app-launch', 'version': '2.1.0', 'filename': 'hl7.fhir.uv.smart-app-launch-2.1.0.tgz'}, {'name': 'hl7.terminology.r4', 'version': '5.0.0', 'filename': 'hl7.terminology.r4-5.0.0.tgz'}, {'name': 'hl7.terminology.r4', 'version': '6.2.0', 'filename': 'hl7.terminology.r4-6.2.0.tgz'}]
|
|
||||||
DEBUG:__main__:Errors during package listing: []
|
|
||||||
DEBUG:__main__:Duplicate groups: {'hl7.fhir.uv.smart-app-launch': ['2.0.0', '2.1.0'], 'hl7.terminology.r4': ['5.0.0', '6.2.0']}
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [21/Apr/2025 23:36:33] "GET /view-igs HTTP/1.1" 200 -
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [21/Apr/2025 23:36:33] "[36mGET /static/FHIRFLARE.png HTTP/1.1[0m" 304 -
|
|
||||||
DEBUG:__main__:Viewing IG hl7.fhir.au.core-1.1.0#preview: 25 profiles, 17 base resources, 1 optional elements
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [21/Apr/2025 23:36:35] "GET /view-ig/1 HTTP/1.1" 200 -
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [21/Apr/2025 23:36:35] "[36mGET /static/FHIRFLARE.png HTTP/1.1[0m" 304 -
|
|
||||||
DEBUG:__main__:Attempting to find SD for 'au-core-patient' in hl7.fhir.au.core-1.1.0-preview.tgz
|
|
||||||
DEBUG:services:Searching for SD matching 'au-core-patient' with profile 'None' in hl7.fhir.au.core-1.1.0-preview.tgz
|
|
||||||
DEBUG:services:Match found based on exact sd_id: au-core-patient
|
|
||||||
DEBUG:services:Removing narrative text from resource: StructureDefinition
|
|
||||||
INFO:services:Found high-confidence match for 'au-core-patient' (package/StructureDefinition-au-core-patient.json), stopping search.
|
|
||||||
DEBUG:__main__:Found 20 unique IDs in differential.
|
|
||||||
DEBUG:__main__:Processing 78 snapshot elements to add isInDifferential flag.
|
|
||||||
DEBUG:__main__:Retrieved 19 Must Support paths for 'au-core-patient' from processed IG DB record.
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [21/Apr/2025 23:36:42] "GET /get-structure?package_name=hl7.fhir.au.core-1.1.0&package_version=preview&resource_type=au-core-patient&view=snapshot HTTP/1.1" 200 -
|
|
||||||
DEBUG:__main__:Scanning packages directory: /app/instance/fhir_packages
|
|
||||||
DEBUG:__main__:Found 9 .tgz files: ['hl7.fhir.au.base-5.1.0-preview.tgz', 'hl7.fhir.au.core-1.1.0-preview.tgz', 'hl7.fhir.r4.core-4.0.1.tgz', 'hl7.fhir.uv.extensions.r4-5.2.0.tgz', 'hl7.fhir.uv.ipa-1.0.0.tgz', 'hl7.fhir.uv.smart-app-launch-2.0.0.tgz', 'hl7.fhir.uv.smart-app-launch-2.1.0.tgz', 'hl7.terminology.r4-5.0.0.tgz', 'hl7.terminology.r4-6.2.0.tgz']
|
|
||||||
DEBUG:__main__:Added package: hl7.fhir.au.base#5.1.0-preview
|
|
||||||
DEBUG:__main__:Added package: hl7.fhir.au.core#1.1.0-preview
|
|
||||||
DEBUG:__main__:Added package: hl7.fhir.r4.core#4.0.1
|
|
||||||
DEBUG:__main__:Added package: hl7.fhir.uv.extensions.r4#5.2.0
|
|
||||||
DEBUG:__main__:Added package: hl7.fhir.uv.ipa#1.0.0
|
|
||||||
DEBUG:__main__:Added package: hl7.fhir.uv.smart-app-launch#2.0.0
|
|
||||||
DEBUG:__main__:Added package: hl7.fhir.uv.smart-app-launch#2.1.0
|
|
||||||
DEBUG:__main__:Added package: hl7.terminology.r4#5.0.0
|
|
||||||
DEBUG:__main__:Added package: hl7.terminology.r4#6.2.0
|
|
||||||
DEBUG:__main__:Set package choices: [('', 'None'), ('hl7.fhir.au.base#5.1.0-preview', 'hl7.fhir.au.base#5.1.0-preview'), ('hl7.fhir.au.core#1.1.0-preview', 'hl7.fhir.au.core#1.1.0-preview'), ('hl7.fhir.r4.core#4.0.1', 'hl7.fhir.r4.core#4.0.1'), ('hl7.fhir.uv.extensions.r4#5.2.0', 'hl7.fhir.uv.extensions.r4#5.2.0'), ('hl7.fhir.uv.ipa#1.0.0', 'hl7.fhir.uv.ipa#1.0.0'), ('hl7.fhir.uv.smart-app-launch#2.0.0', 'hl7.fhir.uv.smart-app-launch#2.0.0'), ('hl7.fhir.uv.smart-app-launch#2.1.0', 'hl7.fhir.uv.smart-app-launch#2.1.0'), ('hl7.terminology.r4#5.0.0', 'hl7.terminology.r4#5.0.0'), ('hl7.terminology.r4#6.2.0', 'hl7.terminology.r4#6.2.0')]
|
|
||||||
DEBUG:__main__:Handling GET request for FSH converter page.
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [21/Apr/2025 23:37:59] "GET /fsh-converter HTTP/1.1" 200 -
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [21/Apr/2025 23:37:59] "[36mGET /static/FHIRFLARE.png HTTP/1.1[0m" 304 -
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [21/Apr/2025 23:37:59] "[36mGET /static/js/lottie.min.js HTTP/1.1[0m" 304 -
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [21/Apr/2025 23:37:59] "[36mGET /static/animations/loading-light.json HTTP/1.1[0m" 304 -
|
|
||||||
DEBUG:__main__:Scanning packages directory: /app/instance/fhir_packages
|
|
||||||
DEBUG:__main__:Found 9 .tgz files: ['hl7.fhir.au.base-5.1.0-preview.tgz', 'hl7.fhir.au.core-1.1.0-preview.tgz', 'hl7.fhir.r4.core-4.0.1.tgz', 'hl7.fhir.uv.extensions.r4-5.2.0.tgz', 'hl7.fhir.uv.ipa-1.0.0.tgz', 'hl7.fhir.uv.smart-app-launch-2.0.0.tgz', 'hl7.fhir.uv.smart-app-launch-2.1.0.tgz', 'hl7.terminology.r4-5.0.0.tgz', 'hl7.terminology.r4-6.2.0.tgz']
|
|
||||||
DEBUG:__main__:Added package: hl7.fhir.au.base#5.1.0-preview
|
|
||||||
DEBUG:__main__:Added package: hl7.fhir.au.core#1.1.0-preview
|
|
||||||
DEBUG:__main__:Added package: hl7.fhir.r4.core#4.0.1
|
|
||||||
DEBUG:__main__:Added package: hl7.fhir.uv.extensions.r4#5.2.0
|
|
||||||
DEBUG:__main__:Added package: hl7.fhir.uv.ipa#1.0.0
|
|
||||||
DEBUG:__main__:Added package: hl7.fhir.uv.smart-app-launch#2.0.0
|
|
||||||
DEBUG:__main__:Added package: hl7.fhir.uv.smart-app-launch#2.1.0
|
|
||||||
DEBUG:__main__:Added package: hl7.terminology.r4#5.0.0
|
|
||||||
DEBUG:__main__:Added package: hl7.terminology.r4#6.2.0
|
|
||||||
DEBUG:__main__:Set package choices: [('', 'None'), ('hl7.fhir.au.base#5.1.0-preview', 'hl7.fhir.au.base#5.1.0-preview'), ('hl7.fhir.au.core#1.1.0-preview', 'hl7.fhir.au.core#1.1.0-preview'), ('hl7.fhir.r4.core#4.0.1', 'hl7.fhir.r4.core#4.0.1'), ('hl7.fhir.uv.extensions.r4#5.2.0', 'hl7.fhir.uv.extensions.r4#5.2.0'), ('hl7.fhir.uv.ipa#1.0.0', 'hl7.fhir.uv.ipa#1.0.0'), ('hl7.fhir.uv.smart-app-launch#2.0.0', 'hl7.fhir.uv.smart-app-launch#2.0.0'), ('hl7.fhir.uv.smart-app-launch#2.1.0', 'hl7.fhir.uv.smart-app-launch#2.1.0'), ('hl7.terminology.r4#5.0.0', 'hl7.terminology.r4#5.0.0'), ('hl7.terminology.r4#6.2.0', 'hl7.terminology.r4#6.2.0')]
|
|
||||||
DEBUG:__main__:Processing input: mode=text, has_file=False, has_text=True, has_alias=False
|
|
||||||
DEBUG:services:Processed input: ('/tmp/tmpkn9pyg0k/input.json', None)
|
|
||||||
DEBUG:__main__:Running GoFSH with input: /tmp/tmpkn9pyg0k/input.json, output_dir: /app/static/uploads/fsh_output
|
|
||||||
DEBUG:services:Wrapper script contents:
|
|
||||||
#!/bin/bash
|
|
||||||
exec 3>/dev/null
|
|
||||||
"gofsh" "/tmp/tmpkn9pyg0k/input.json" "-o" "/tmp/tmp37ihga7h" "-s" "file-per-definition" "-l" "error" "-u" "4.3.0" "--dependency" "hl7.fhir.us.core@6.1.0" "--indent" </dev/null >/tmp/gofsh_output.log 2>&1
|
|
||||||
|
|
||||||
DEBUG:services:Temp output directory contents before GoFSH: []
|
|
||||||
DEBUG:services:GoFSH output:
|
|
||||||
|
|
||||||
╔═════════════════════════ GoFSH RESULTS ═════════════════════════╗
|
|
||||||
║ ╭────────────────────┬───────────────────┬────────────────────╮ ║
|
|
||||||
║ │ Profiles │ Extensions │ Logicals │ ║
|
|
||||||
║ ├────────────────────┼───────────────────┼────────────────────┤ ║
|
|
||||||
║ │ 0 │ 0 │ 0 │ ║
|
|
||||||
║ ╰────────────────────┴───────────────────┴────────────────────╯ ║
|
|
||||||
║ ╭────────────────────┬───────────────────┬────────────────────╮ ║
|
|
||||||
║ │ Resources │ ValueSets │ CodeSystems │ ║
|
|
||||||
║ ├────────────────────┼───────────────────┼────────────────────┤ ║
|
|
||||||
║ │ 0 │ 0 │ 0 │ ║
|
|
||||||
║ ╰────────────────────┴───────────────────┴────────────────────╯ ║
|
|
||||||
║ ╭────────────────────┬───────────────────┬────────────────────╮ ║
|
|
||||||
║ │ Instances │ Invariants │ Mappings │ ║
|
|
||||||
║ ├────────────────────┼───────────────────┼────────────────────┤ ║
|
|
||||||
║ │ 1 │ 0 │ 0 │ ║
|
|
||||||
║ ╰────────────────────┴───────────────────┴────────────────────╯ ║
|
|
||||||
║ ╭────────────────────┬───────────────────┬────────────────────╮ ║
|
|
||||||
║ │ Aliases │ │ │ ║
|
|
||||||
║ ├────────────────────┼───────────────────┼────────────────────┤ ║
|
|
||||||
║ │ 1 │ │ │ ║
|
|
||||||
║ ╰────────────────────┴───────────────────┴────────────────────╯ ║
|
|
||||||
║ ║
|
|
||||||
╠═════════════════════════════════════════════════════════════════╣
|
|
||||||
║ Not bad, but it cod be batter! 0 Errors 1 Warning ║
|
|
||||||
╚═════════════════════════════════════════════════════════════════╝
|
|
||||||
|
|
||||||
DEBUG:services:GoFSH fishing-trip wrapper script contents:
|
|
||||||
#!/bin/bash
|
|
||||||
exec 3>/dev/null
|
|
||||||
exec >/dev/null 2>&1
|
|
||||||
"gofsh" "/tmp/tmpkn9pyg0k/input.json" "-o" "/tmp/tmpb_q8uf73" "-s" "file-per-definition" "-l" "error" "--fshing-trip" "-u" "4.3.0" "--dependency" "hl7.fhir.us.core@6.1.0" "--indent" </dev/null >/tmp/gofsh_output.log 2>&1
|
|
||||||
|
|
||||||
DEBUG:services:GoFSH fishing-trip output:
|
|
||||||
|
|
||||||
╔═════════════════════════ GoFSH RESULTS ═════════════════════════╗
|
|
||||||
║ ╭────────────────────┬───────────────────┬────────────────────╮ ║
|
|
||||||
║ │ Profiles │ Extensions │ Logicals │ ║
|
|
||||||
║ ├────────────────────┼───────────────────┼────────────────────┤ ║
|
|
||||||
║ │ 0 │ 0 │ 0 │ ║
|
|
||||||
║ ╰────────────────────┴───────────────────┴────────────────────╯ ║
|
|
||||||
║ ╭────────────────────┬───────────────────┬────────────────────╮ ║
|
|
||||||
║ │ Resources │ ValueSets │ CodeSystems │ ║
|
|
||||||
║ ├────────────────────┼───────────────────┼────────────────────┤ ║
|
|
||||||
║ │ 0 │ 0 │ 0 │ ║
|
|
||||||
║ ╰────────────────────┴───────────────────┴────────────────────╯ ║
|
|
||||||
║ ╭────────────────────┬───────────────────┬────────────────────╮ ║
|
|
||||||
║ │ Instances │ Invariants │ Mappings │ ║
|
|
||||||
║ ├────────────────────┼───────────────────┼────────────────────┤ ║
|
|
||||||
║ │ 1 │ 0 │ 0 │ ║
|
|
||||||
║ ╰────────────────────┴───────────────────┴────────────────────╯ ║
|
|
||||||
║ ╭────────────────────┬───────────────────┬────────────────────╮ ║
|
|
||||||
║ │ Aliases │ │ │ ║
|
|
||||||
║ ├────────────────────┼───────────────────┼────────────────────┤ ║
|
|
||||||
║ │ 1 │ │ │ ║
|
|
||||||
║ ╰────────────────────┴───────────────────┴────────────────────╯ ║
|
|
||||||
║ ║
|
|
||||||
╠═════════════════════════════════════════════════════════════════╣
|
|
||||||
║ Warnings... Water those about? 0 Errors 1 Warning ║
|
|
||||||
╚═════════════════════════════════════════════════════════════════╝
|
|
||||||
╔═════════════════════════════════════════════════════════════════╗
|
|
||||||
║ Generating round trip results via SUSHI ║
|
|
||||||
╚═════════════════════════════════════════════════════════════════╝
|
|
||||||
info Running SUSHI v3.15.0 (implements FHIR Shorthand specification v3.0.0)
|
|
||||||
info Arguments:
|
|
||||||
info /tmp/tmpb_q8uf73
|
|
||||||
info No output path specified. Output to /tmp/tmpb_q8uf73
|
|
||||||
info Using configuration file: /tmp/tmpb_q8uf73/sushi-config.yaml
|
|
||||||
warn The FSHOnly property is set to true, so no output specific to IG creation will be generated. The following properties are unused and only relevant for IG creation: id, name. Consider removing these properties from sushi-config.yaml.
|
|
||||||
File: /tmp/tmpb_q8uf73/sushi-config.yaml
|
|
||||||
info Importing FSH text...
|
|
||||||
info Preprocessed 2 documents with 1 aliases.
|
|
||||||
info Imported 0 definitions and 1 instances.
|
|
||||||
info Loaded virtual package sushi-r5forR4#1.0.0 with 7 resources
|
|
||||||
info Resolved hl7.fhir.uv.tools.r4#latest to concrete version 0.5.0
|
|
||||||
info Loaded hl7.fhir.uv.tools.r4#0.5.0 with 88 resources
|
|
||||||
info Resolved hl7.terminology.r4#latest to concrete version 6.2.0
|
|
||||||
info Loaded hl7.terminology.r4#6.2.0 with 4323 resources
|
|
||||||
info Resolved hl7.fhir.uv.extensions.r4#latest to concrete version 5.2.0
|
|
||||||
info Loaded hl7.fhir.uv.extensions.r4#5.2.0 with 759 resources
|
|
||||||
info Loaded hl7.fhir.us.core#6.1.0 with 210 resources
|
|
||||||
info Loaded hl7.fhir.r4b.core#4.3.0 with 3497 resources
|
|
||||||
info Loaded virtual package sushi-local#LOCAL with 0 resources
|
|
||||||
info Converting FSH to FHIR resources...
|
|
||||||
info Converted 1 FHIR instances.
|
|
||||||
info Exporting FHIR resources as JSON...
|
|
||||||
info Exported 1 FHIR resources as JSON.
|
|
||||||
info Exporting FSH definitions only. No IG related content will be exported.
|
|
||||||
|
|
||||||
========================= SUSHI RESULTS ===========================
|
|
||||||
| ------------------------------------------------------------- |
|
|
||||||
| | Profiles | Extensions | Logicals | Resources | |
|
|
||||||
| |-------------------------------------------------------------| |
|
|
||||||
| | 0 | 0 | 0 | 0 | |
|
|
||||||
| ------------------------------------------------------------- |
|
|
||||||
| ------------------------------------------------------------- |
|
|
||||||
| | ValueSets | CodeSystems | Instances | |
|
|
||||||
| |-------------------------------------------------------------| |
|
|
||||||
| | 0 | 0 | 1 | |
|
|
||||||
| ------------------------------------------------------------- |
|
|
||||||
| |
|
|
||||||
===================================================================
|
|
||||||
| You might need some Vitamin Sea 0 Errors 1 Warning |
|
|
||||||
===================================================================
|
|
||||||
|
|
||||||
|
|
||||||
DEBUG:services:Copied files to final output directory: ['sushi-config.yaml', 'input/fsh/aliases.fsh', 'input/fsh/instances/discharge-1.fsh', 'input/input.json', 'fshing-trip-comparison.html']
|
|
||||||
INFO:services:GoFSH executed successfully for /tmp/tmpkn9pyg0k/input.json
|
|
||||||
DEBUG:__main__:Successfully removed temp directory: /tmp/tmpkn9pyg0k
|
|
||||||
INFO:__main__:FSH conversion successful
|
|
||||||
DEBUG:__main__:Returning partial HTML for AJAX POST request.
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [21/Apr/2025 23:40:26] "POST /fsh-converter HTTP/1.1" 200 -
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [21/Apr/2025 23:40:26] "[36mGET /static/animations/loading-light.json HTTP/1.1[0m" 304 -
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [21/Apr/2025 23:40:42] "GET /static/uploads/fsh_output/fshing-trip-comparison.html HTTP/1.1" 200 -
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [21/Apr/2025 23:40:59] "GET /fhir-ui-operations HTTP/1.1" 200 -
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [21/Apr/2025 23:41:00] "[36mGET /static/FHIRFLARE.png HTTP/1.1[0m" 304 -
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [21/Apr/2025 23:43:27] "GET /fhir HTTP/1.1" 200 -
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [21/Apr/2025 23:43:27] "[36mGET /static/FHIRFLARE.png HTTP/1.1[0m" 304 -
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [21/Apr/2025 23:43:31] "GET /fhir-ui-operations HTTP/1.1" 200 -
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [21/Apr/2025 23:43:31] "[36mGET /static/FHIRFLARE.png HTTP/1.1[0m" 304 -
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [21/Apr/2025 23:43:39] "GET /fhir HTTP/1.1" 200 -
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [21/Apr/2025 23:43:39] "[36mGET /static/FHIRFLARE.png HTTP/1.1[0m" 304 -
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [21/Apr/2025 23:43:48] "GET /validate-sample HTTP/1.1" 200 -
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [21/Apr/2025 23:43:48] "[36mGET /static/FHIRFLARE.png HTTP/1.1[0m" 304 -
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [21/Apr/2025 23:44:08] "GET / HTTP/1.1" 200 -
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [21/Apr/2025 23:44:08] "[36mGET /static/FHIRFLARE.png HTTP/1.1[0m" 304 -
|
|
||||||
DEBUG:__main__:Scanning packages directory: /app/instance/fhir_packages
|
|
||||||
DEBUG:services:Parsed 'hl7.fhir.au.base-5.1.0-preview.tgz' -> name='hl7.fhir.au.base-5.1.0', version='preview'
|
|
||||||
DEBUG:services:Parsed 'hl7.fhir.au.core-1.1.0-preview.tgz' -> name='hl7.fhir.au.core-1.1.0', version='preview'
|
|
||||||
DEBUG:services:Parsed 'hl7.fhir.r4.core-4.0.1.tgz' -> name='hl7.fhir.r4.core', version='4.0.1'
|
|
||||||
DEBUG:services:Parsed 'hl7.fhir.uv.extensions.r4-5.2.0.tgz' -> name='hl7.fhir.uv.extensions.r4', version='5.2.0'
|
|
||||||
DEBUG:services:Parsed 'hl7.fhir.uv.ipa-1.0.0.tgz' -> name='hl7.fhir.uv.ipa', version='1.0.0'
|
|
||||||
DEBUG:services:Parsed 'hl7.fhir.uv.smart-app-launch-2.0.0.tgz' -> name='hl7.fhir.uv.smart-app-launch', version='2.0.0'
|
|
||||||
DEBUG:services:Parsed 'hl7.fhir.uv.smart-app-launch-2.1.0.tgz' -> name='hl7.fhir.uv.smart-app-launch', version='2.1.0'
|
|
||||||
DEBUG:services:Parsed 'hl7.terminology.r4-5.0.0.tgz' -> name='hl7.terminology.r4', version='5.0.0'
|
|
||||||
DEBUG:services:Parsed 'hl7.terminology.r4-6.2.0.tgz' -> name='hl7.terminology.r4', version='6.2.0'
|
|
||||||
DEBUG:__main__:Found packages: [{'name': 'hl7.fhir.au.base', 'version': '5.1.0-preview', 'filename': 'hl7.fhir.au.base-5.1.0-preview.tgz'}, {'name': 'hl7.fhir.au.core', 'version': '1.1.0-preview', 'filename': 'hl7.fhir.au.core-1.1.0-preview.tgz'}, {'name': 'hl7.fhir.r4.core', 'version': '4.0.1', 'filename': 'hl7.fhir.r4.core-4.0.1.tgz'}, {'name': 'hl7.fhir.uv.extensions.r4', 'version': '5.2.0', 'filename': 'hl7.fhir.uv.extensions.r4-5.2.0.tgz'}, {'name': 'hl7.fhir.uv.ipa', 'version': '1.0.0', 'filename': 'hl7.fhir.uv.ipa-1.0.0.tgz'}, {'name': 'hl7.fhir.uv.smart-app-launch', 'version': '2.0.0', 'filename': 'hl7.fhir.uv.smart-app-launch-2.0.0.tgz'}, {'name': 'hl7.fhir.uv.smart-app-launch', 'version': '2.1.0', 'filename': 'hl7.fhir.uv.smart-app-launch-2.1.0.tgz'}, {'name': 'hl7.terminology.r4', 'version': '5.0.0', 'filename': 'hl7.terminology.r4-5.0.0.tgz'}, {'name': 'hl7.terminology.r4', 'version': '6.2.0', 'filename': 'hl7.terminology.r4-6.2.0.tgz'}]
|
|
||||||
DEBUG:__main__:Errors during package listing: []
|
|
||||||
DEBUG:__main__:Duplicate groups: {'hl7.fhir.uv.smart-app-launch': ['2.0.0', '2.1.0'], 'hl7.terminology.r4': ['5.0.0', '6.2.0']}
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [21/Apr/2025 23:45:07] "GET /view-igs HTTP/1.1" 200 -
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [21/Apr/2025 23:45:07] "GET /static/FHIRFLARE.png HTTP/1.1" 200 -
|
|
||||||
DEBUG:__main__:Viewing IG hl7.fhir.au.core-1.1.0#preview: 25 profiles, 17 base resources, 1 optional elements
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [21/Apr/2025 23:45:13] "GET /view-ig/1 HTTP/1.1" 200 -
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [21/Apr/2025 23:45:13] "[36mGET /static/FHIRFLARE.png HTTP/1.1[0m" 304 -
|
|
||||||
DEBUG:__main__:Attempting to find SD for 'au-core-patient' in hl7.fhir.au.core-1.1.0-preview.tgz
|
|
||||||
DEBUG:services:Searching for SD matching 'au-core-patient' with profile 'None' in hl7.fhir.au.core-1.1.0-preview.tgz
|
|
||||||
DEBUG:services:Match found based on exact sd_id: au-core-patient
|
|
||||||
DEBUG:services:Removing narrative text from resource: StructureDefinition
|
|
||||||
INFO:services:Found high-confidence match for 'au-core-patient' (package/StructureDefinition-au-core-patient.json), stopping search.
|
|
||||||
DEBUG:__main__:Found 20 unique IDs in differential.
|
|
||||||
DEBUG:__main__:Processing 78 snapshot elements to add isInDifferential flag.
|
|
||||||
DEBUG:__main__:Retrieved 19 Must Support paths for 'au-core-patient' from processed IG DB record.
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [21/Apr/2025 23:45:17] "GET /get-structure?package_name=hl7.fhir.au.core-1.1.0&package_version=preview&resource_type=au-core-patient&view=snapshot HTTP/1.1" 200 -
|
|
||||||
DEBUG:__main__:Removing narrative text from example 'package/example/Patient-banks-mia-leanne.json'
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [21/Apr/2025 23:45:32] "GET /get-example?package_name=hl7.fhir.au.core-1.1.0&package_version=preview&filename=package/example/Patient-banks-mia-leanne.json HTTP/1.1" 200 -
|
|
||||||
INFO:__main__:Application running in mode: lite
|
|
||||||
DEBUG:__main__:Instance path configuration: /app/instance
|
|
||||||
DEBUG:__main__:Database URI: sqlite:////app/instance/fhir_ig.db
|
|
||||||
DEBUG:__main__:Packages path: /app/instance/fhir_packages
|
|
||||||
DEBUG:__main__:Flask instance folder path: /app/instance
|
|
||||||
DEBUG:__main__:Directories created/verified: Instance: /app/instance, Packages: /app/instance/fhir_packages
|
|
||||||
DEBUG:__main__:Attempting to create database tables for URI: sqlite:////app/instance/fhir_ig.db
|
|
||||||
INFO:__main__:Database tables created successfully (if they didn't exist).
|
|
||||||
DEBUG:__main__:Instance path configuration: /app/instance
|
|
||||||
DEBUG:__main__:Database URI: sqlite:////app/instance/fhir_ig.db
|
|
||||||
DEBUG:__main__:Packages path: /app/instance/fhir_packages
|
|
||||||
DEBUG:__main__:Flask instance folder path: /app/instance
|
|
||||||
DEBUG:__main__:Directories created/verified: Instance: /app/instance, Packages: /app/instance/fhir_packages
|
|
||||||
DEBUG:__main__:Attempting to create database tables for URI: sqlite:////app/instance/fhir_ig.db
|
|
||||||
INFO:__main__:Database tables created successfully (if they didn't exist).
|
|
||||||
INFO:werkzeug:[31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
|
||||||
* Running on all addresses (0.0.0.0)
|
|
||||||
* Running on http://127.0.0.1:5000
|
|
||||||
* Running on http://172.18.0.2:5000
|
|
||||||
INFO:werkzeug:[33mPress CTRL+C to quit[0m
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [22/Apr/2025 00:12:38] "GET / HTTP/1.1" 200 -
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [22/Apr/2025 00:12:38] "[36mGET /static/FHIRFLARE.png HTTP/1.1[0m" 304 -
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [22/Apr/2025 00:12:44] "GET /about HTTP/1.1" 200 -
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [22/Apr/2025 00:12:44] "[36mGET /static/FHIRFLARE.png HTTP/1.1[0m" 304 -
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [22/Apr/2025 00:13:32] "GET / HTTP/1.1" 200 -
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [22/Apr/2025 00:13:32] "[36mGET /static/FHIRFLARE.png HTTP/1.1[0m" 304 -
|
|
||||||
INFO:__main__:Application running in mode: lite
|
|
||||||
DEBUG:__main__:Instance path configuration: /app/instance
|
|
||||||
DEBUG:__main__:Database URI: sqlite:////app/instance/fhir_ig.db
|
|
||||||
DEBUG:__main__:Packages path: /app/instance/fhir_packages
|
|
||||||
DEBUG:__main__:Flask instance folder path: /app/instance
|
|
||||||
DEBUG:__main__:Directories created/verified: Instance: /app/instance, Packages: /app/instance/fhir_packages
|
|
||||||
DEBUG:__main__:Attempting to create database tables for URI: sqlite:////app/instance/fhir_ig.db
|
|
||||||
INFO:__main__:Database tables created successfully (if they didn't exist).
|
|
||||||
DEBUG:__main__:Instance path configuration: /app/instance
|
|
||||||
DEBUG:__main__:Database URI: sqlite:////app/instance/fhir_ig.db
|
|
||||||
DEBUG:__main__:Packages path: /app/instance/fhir_packages
|
|
||||||
DEBUG:__main__:Flask instance folder path: /app/instance
|
|
||||||
DEBUG:__main__:Directories created/verified: Instance: /app/instance, Packages: /app/instance/fhir_packages
|
|
||||||
DEBUG:__main__:Attempting to create database tables for URI: sqlite:////app/instance/fhir_ig.db
|
|
||||||
INFO:__main__:Database tables created successfully (if they didn't exist).
|
|
||||||
INFO:werkzeug:[31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
|
||||||
* Running on all addresses (0.0.0.0)
|
|
||||||
* Running on http://127.0.0.1:5000
|
|
||||||
* Running on http://172.18.0.2:5000
|
|
||||||
INFO:werkzeug:[33mPress CTRL+C to quit[0m
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [22/Apr/2025 00:28:51] "GET / HTTP/1.1" 200 -
|
|
||||||
INFO:werkzeug:172.18.0.1 - - [22/Apr/2025 00:28:52] "[36mGET /static/FHIRFLARE.png HTTP/1.1[0m" 304 -
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
2025-04-21 23:28:14,353 CRIT Supervisor is running as root. Privileges were not dropped because no user is specified in the config file. If you intend to run as root, you can set user=root in the config file to avoid this message.
|
|
||||||
2025-04-21 23:28:14,360 INFO supervisord started with pid 1
|
|
||||||
2025-04-21 23:28:15,369 INFO spawned: 'flask' with pid 8
|
|
||||||
2025-04-21 23:28:15,377 INFO spawned: 'tomcat' with pid 9
|
|
||||||
2025-04-21 23:28:26,240 INFO success: flask entered RUNNING state, process has stayed up for > than 10 seconds (startsecs)
|
|
||||||
2025-04-21 23:28:46,263 INFO success: tomcat entered RUNNING state, process has stayed up for > than 30 seconds (startsecs)
|
|
||||||
2025-04-21 23:32:40,570 WARN received SIGTERM indicating exit request
|
|
||||||
2025-04-21 23:32:40,603 INFO waiting for flask, tomcat to die
|
|
||||||
2025-04-21 23:32:42,242 WARN stopped: tomcat (exit status 143)
|
|
||||||
2025-04-21 23:32:43,301 WARN stopped: flask (terminated by SIGTERM)
|
|
||||||
2025-04-21 23:36:02,369 CRIT Supervisor is running as root. Privileges were not dropped because no user is specified in the config file. If you intend to run as root, you can set user=root in the config file to avoid this message.
|
|
||||||
2025-04-21 23:36:02,377 INFO supervisord started with pid 1
|
|
||||||
2025-04-21 23:36:03,393 INFO spawned: 'flask' with pid 7
|
|
||||||
2025-04-21 23:36:03,407 INFO spawned: 'tomcat' with pid 8
|
|
||||||
2025-04-21 23:36:13,571 INFO success: flask entered RUNNING state, process has stayed up for > than 10 seconds (startsecs)
|
|
||||||
2025-04-21 23:36:34,045 INFO success: tomcat entered RUNNING state, process has stayed up for > than 30 seconds (startsecs)
|
|
||||||
2025-04-21 23:51:48,038 WARN received SIGTERM indicating exit request
|
|
||||||
2025-04-21 23:51:48,042 INFO waiting for flask, tomcat to die
|
|
||||||
2025-04-21 23:51:50,438 WARN stopped: tomcat (exit status 143)
|
|
||||||
2025-04-21 23:51:51,472 WARN stopped: flask (terminated by SIGTERM)
|
|
||||||
2025-04-22 00:09:45,824 CRIT Supervisor is running as root. Privileges were not dropped because no user is specified in the config file. If you intend to run as root, you can set user=root in the config file to avoid this message.
|
|
||||||
2025-04-22 00:09:45,840 INFO supervisord started with pid 1
|
|
||||||
2025-04-22 00:09:46,858 INFO spawned: 'flask' with pid 7
|
|
||||||
2025-04-22 00:09:46,862 INFO spawned: 'tomcat' with pid 8
|
|
||||||
2025-04-22 00:09:57,177 INFO success: flask entered RUNNING state, process has stayed up for > than 10 seconds (startsecs)
|
|
||||||
2025-04-22 00:10:17,198 INFO success: tomcat entered RUNNING state, process has stayed up for > than 30 seconds (startsecs)
|
|
||||||
2025-04-22 00:25:04,477 WARN received SIGTERM indicating exit request
|
|
||||||
2025-04-22 00:25:04,493 INFO waiting for flask, tomcat to die
|
|
||||||
2025-04-22 00:25:05,830 WARN stopped: tomcat (exit status 143)
|
|
||||||
2025-04-22 00:25:06,852 WARN stopped: flask (terminated by SIGTERM)
|
|
||||||
2025-04-22 00:27:24,713 CRIT Supervisor is running as root. Privileges were not dropped because no user is specified in the config file. If you intend to run as root, you can set user=root in the config file to avoid this message.
|
|
||||||
2025-04-22 00:27:24,719 INFO supervisord started with pid 1
|
|
||||||
2025-04-22 00:27:25,731 INFO spawned: 'flask' with pid 7
|
|
||||||
2025-04-22 00:27:25,736 INFO spawned: 'tomcat' with pid 8
|
|
||||||
2025-04-22 00:27:36,540 INFO success: flask entered RUNNING state, process has stayed up for > than 10 seconds (startsecs)
|
|
||||||
2025-04-22 00:27:56,565 INFO success: tomcat entered RUNNING state, process has stayed up for > than 30 seconds (startsecs)
|
|
||||||
2025-04-22 00:54:33,995 WARN received SIGTERM indicating exit request
|
|
||||||
2025-04-22 00:54:34,047 INFO waiting for flask, tomcat to die
|
|
||||||
2025-04-22 00:54:34,740 WARN stopped: tomcat (exit status 143)
|
|
||||||
2025-04-22 00:54:35,770 WARN stopped: flask (terminated by SIGTERM)
|
|
||||||
@ -1 +0,0 @@
|
|||||||
1
|
|
||||||
@ -1,680 +0,0 @@
|
|||||||
21-Apr-2025 23:28:16.155 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version name: Apache Tomcat/10.1.40
|
|
||||||
21-Apr-2025 23:28:16.164 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server built: Apr 1 2025 17:20:53 UTC
|
|
||||||
21-Apr-2025 23:28:16.164 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version number: 10.1.40.0
|
|
||||||
21-Apr-2025 23:28:16.164 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Name: Linux
|
|
||||||
21-Apr-2025 23:28:16.164 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Version: 5.15.167.4-microsoft-standard-WSL2
|
|
||||||
21-Apr-2025 23:28:16.164 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Architecture: amd64
|
|
||||||
21-Apr-2025 23:28:16.165 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Java Home: /opt/java/openjdk
|
|
||||||
21-Apr-2025 23:28:16.165 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Version: 17.0.14+7
|
|
||||||
21-Apr-2025 23:28:16.165 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Vendor: Eclipse Adoptium
|
|
||||||
21-Apr-2025 23:28:16.165 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_BASE: /usr/local/tomcat
|
|
||||||
21-Apr-2025 23:28:16.165 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_HOME: /usr/local/tomcat
|
|
||||||
21-Apr-2025 23:28:16.187 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties
|
|
||||||
21-Apr-2025 23:28:16.188 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
|
|
||||||
21-Apr-2025 23:28:16.188 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djdk.tls.ephemeralDHKeySize=2048
|
|
||||||
21-Apr-2025 23:28:16.188 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.protocol.handler.pkgs=org.apache.catalina.webresources
|
|
||||||
21-Apr-2025 23:28:16.188 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dsun.io.useCanonCaches=false
|
|
||||||
21-Apr-2025 23:28:16.188 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dorg.apache.catalina.security.SecurityListener.UMASK=0027
|
|
||||||
21-Apr-2025 23:28:16.188 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.lang=ALL-UNNAMED
|
|
||||||
21-Apr-2025 23:28:16.188 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.lang.reflect=ALL-UNNAMED
|
|
||||||
21-Apr-2025 23:28:16.188 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.io=ALL-UNNAMED
|
|
||||||
21-Apr-2025 23:28:16.189 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.util=ALL-UNNAMED
|
|
||||||
21-Apr-2025 23:28:16.189 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.util.concurrent=ALL-UNNAMED
|
|
||||||
21-Apr-2025 23:28:16.189 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED
|
|
||||||
21-Apr-2025 23:28:16.189 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcatalina.base=/usr/local/tomcat
|
|
||||||
21-Apr-2025 23:28:16.189 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcatalina.home=/usr/local/tomcat
|
|
||||||
21-Apr-2025 23:28:16.189 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.io.tmpdir=/usr/local/tomcat/temp
|
|
||||||
21-Apr-2025 23:28:16.196 INFO [main] org.apache.catalina.core.AprLifecycleListener.lifecycleEvent Loaded Apache Tomcat Native library [2.0.8] using APR version [1.7.2].
|
|
||||||
21-Apr-2025 23:28:16.200 INFO [main] org.apache.catalina.core.AprLifecycleListener.initializeSSL OpenSSL successfully initialized [OpenSSL 3.0.13 30 Jan 2024]
|
|
||||||
21-Apr-2025 23:28:16.564 INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["http-nio-8080"]
|
|
||||||
21-Apr-2025 23:28:16.604 INFO [main] org.apache.catalina.startup.Catalina.load Server initialization in [733] milliseconds
|
|
||||||
21-Apr-2025 23:28:16.680 INFO [main] org.apache.catalina.core.StandardService.startInternal Starting service [Catalina]
|
|
||||||
21-Apr-2025 23:28:16.680 INFO [main] org.apache.catalina.core.StandardEngine.startInternal Starting Servlet engine: [Apache Tomcat/10.1.40]
|
|
||||||
21-Apr-2025 23:28:16.703 INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deploying web application archive [/usr/local/tomcat/webapps/ROOT.war]
|
|
||||||
21-Apr-2025 23:28:16.727 SEVERE [main] org.apache.catalina.startup.ContextConfig.beforeStart Exception fixing docBase for context []
|
|
||||||
java.util.zip.ZipException: zip END header not found
|
|
||||||
at java.base/java.util.zip.ZipFile$Source.findEND(ZipFile.java:1637)
|
|
||||||
at java.base/java.util.zip.ZipFile$Source.initCEN(ZipFile.java:1645)
|
|
||||||
at java.base/java.util.zip.ZipFile$Source.<init>(ZipFile.java:1483)
|
|
||||||
at java.base/java.util.zip.ZipFile$Source.get(ZipFile.java:1445)
|
|
||||||
at java.base/java.util.zip.ZipFile$CleanableResource.<init>(ZipFile.java:717)
|
|
||||||
at java.base/java.util.zip.ZipFile.<init>(ZipFile.java:251)
|
|
||||||
at java.base/java.util.zip.ZipFile.<init>(ZipFile.java:180)
|
|
||||||
at java.base/java.util.jar.JarFile.<init>(JarFile.java:346)
|
|
||||||
at java.base/sun.net.www.protocol.jar.URLJarFile.<init>(URLJarFile.java:103)
|
|
||||||
at java.base/sun.net.www.protocol.jar.URLJarFile.getJarFile(URLJarFile.java:72)
|
|
||||||
at java.base/sun.net.www.protocol.jar.JarFileFactory.get(JarFileFactory.java:168)
|
|
||||||
at java.base/sun.net.www.protocol.jar.JarURLConnection.connect(JarURLConnection.java:131)
|
|
||||||
at java.base/sun.net.www.protocol.jar.JarURLConnection.getJarFile(JarURLConnection.java:92)
|
|
||||||
at org.apache.catalina.startup.ExpandWar.expand(ExpandWar.java:123)
|
|
||||||
at org.apache.catalina.startup.ContextConfig.fixDocBase(ContextConfig.java:812)
|
|
||||||
at org.apache.catalina.startup.ContextConfig.beforeStart(ContextConfig.java:948)
|
|
||||||
at org.apache.catalina.startup.ContextConfig.lifecycleEvent(ContextConfig.java:292)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:109)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.setStateInternal(LifecycleBase.java:389)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:163)
|
|
||||||
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:599)
|
|
||||||
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:571)
|
|
||||||
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:654)
|
|
||||||
at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:964)
|
|
||||||
at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1902)
|
|
||||||
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)
|
|
||||||
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
|
|
||||||
at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
|
|
||||||
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:123)
|
|
||||||
at org.apache.catalina.startup.HostConfig.deployWARs(HostConfig.java:769)
|
|
||||||
at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:420)
|
|
||||||
at org.apache.catalina.startup.HostConfig.start(HostConfig.java:1620)
|
|
||||||
at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:303)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:109)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.setStateInternal(LifecycleBase.java:389)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.setState(LifecycleBase.java:336)
|
|
||||||
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:776)
|
|
||||||
at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:772)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1203)
|
|
||||||
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1193)
|
|
||||||
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
|
|
||||||
at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
|
|
||||||
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:145)
|
|
||||||
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:749)
|
|
||||||
at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:203)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.core.StandardService.startInternal(StandardService.java:412)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:870)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.startup.Catalina.start(Catalina.java:761)
|
|
||||||
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
|
|
||||||
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
|
|
||||||
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
|
|
||||||
at java.base/java.lang.reflect.Method.invoke(Method.java:569)
|
|
||||||
at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:345)
|
|
||||||
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:476)
|
|
||||||
21-Apr-2025 23:28:16.769 SEVERE [main] org.apache.catalina.startup.HostConfig.deployWAR Error deploying web application archive [/usr/local/tomcat/webapps/ROOT.war]
|
|
||||||
java.lang.IllegalStateException: Error starting child
|
|
||||||
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:602)
|
|
||||||
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:571)
|
|
||||||
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:654)
|
|
||||||
at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:964)
|
|
||||||
at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1902)
|
|
||||||
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)
|
|
||||||
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
|
|
||||||
at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
|
|
||||||
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:123)
|
|
||||||
at org.apache.catalina.startup.HostConfig.deployWARs(HostConfig.java:769)
|
|
||||||
at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:420)
|
|
||||||
at org.apache.catalina.startup.HostConfig.start(HostConfig.java:1620)
|
|
||||||
at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:303)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:109)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.setStateInternal(LifecycleBase.java:389)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.setState(LifecycleBase.java:336)
|
|
||||||
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:776)
|
|
||||||
at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:772)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1203)
|
|
||||||
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1193)
|
|
||||||
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
|
|
||||||
at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
|
|
||||||
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:145)
|
|
||||||
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:749)
|
|
||||||
at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:203)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.core.StandardService.startInternal(StandardService.java:412)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:870)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.startup.Catalina.start(Catalina.java:761)
|
|
||||||
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
|
|
||||||
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
|
|
||||||
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
|
|
||||||
at java.base/java.lang.reflect.Method.invoke(Method.java:569)
|
|
||||||
at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:345)
|
|
||||||
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:476)
|
|
||||||
Caused by: org.apache.catalina.LifecycleException: Failed to initialize component [org.apache.catalina.webresources.WarResourceSet@5a5a729f]
|
|
||||||
at org.apache.catalina.util.LifecycleBase.handleSubClassException(LifecycleBase.java:406)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:125)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:155)
|
|
||||||
at org.apache.catalina.webresources.StandardRoot.startInternal(StandardRoot.java:723)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.core.StandardContext.resourcesStart(StandardContext.java:4159)
|
|
||||||
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:4281)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:599)
|
|
||||||
... 37 more
|
|
||||||
Caused by: java.lang.IllegalArgumentException: java.util.zip.ZipException: zip END header not found
|
|
||||||
at org.apache.catalina.webresources.AbstractSingleArchiveResourceSet.initInternal(AbstractSingleArchiveResourceSet.java:141)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:122)
|
|
||||||
... 44 more
|
|
||||||
Caused by: java.util.zip.ZipException: zip END header not found
|
|
||||||
at java.base/java.util.zip.ZipFile$Source.findEND(ZipFile.java:1637)
|
|
||||||
at java.base/java.util.zip.ZipFile$Source.initCEN(ZipFile.java:1645)
|
|
||||||
at java.base/java.util.zip.ZipFile$Source.<init>(ZipFile.java:1483)
|
|
||||||
at java.base/java.util.zip.ZipFile$Source.get(ZipFile.java:1445)
|
|
||||||
at java.base/java.util.zip.ZipFile$CleanableResource.<init>(ZipFile.java:717)
|
|
||||||
at java.base/java.util.zip.ZipFile.<init>(ZipFile.java:251)
|
|
||||||
at java.base/java.util.zip.ZipFile.<init>(ZipFile.java:180)
|
|
||||||
at java.base/java.util.jar.JarFile.<init>(JarFile.java:346)
|
|
||||||
at org.apache.catalina.webresources.AbstractSingleArchiveResourceSet.initInternal(AbstractSingleArchiveResourceSet.java:138)
|
|
||||||
... 45 more
|
|
||||||
21-Apr-2025 23:28:16.773 INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deployment of web application archive [/usr/local/tomcat/webapps/ROOT.war] has finished in [68] ms
|
|
||||||
21-Apr-2025 23:28:16.774 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [/usr/local/tomcat/webapps/custom]
|
|
||||||
21-Apr-2025 23:28:17.183 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/usr/local/tomcat/webapps/custom] has finished in [409] ms
|
|
||||||
21-Apr-2025 23:28:17.184 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [/usr/local/tomcat/webapps/app]
|
|
||||||
21-Apr-2025 23:28:17.203 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/usr/local/tomcat/webapps/app] has finished in [19] ms
|
|
||||||
21-Apr-2025 23:28:17.208 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080"]
|
|
||||||
21-Apr-2025 23:28:17.227 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [623] milliseconds
|
|
||||||
21-Apr-2025 23:32:40.652 INFO [Thread-1] org.apache.coyote.AbstractProtocol.pause Pausing ProtocolHandler ["http-nio-8080"]
|
|
||||||
21-Apr-2025 23:32:40.760 INFO [Thread-1] org.apache.catalina.core.StandardService.stopInternal Stopping service [Catalina]
|
|
||||||
21-Apr-2025 23:32:40.883 INFO [Thread-1] org.apache.coyote.AbstractProtocol.stop Stopping ProtocolHandler ["http-nio-8080"]
|
|
||||||
21-Apr-2025 23:32:40.910 INFO [Thread-1] org.apache.coyote.AbstractProtocol.destroy Destroying ProtocolHandler ["http-nio-8080"]
|
|
||||||
21-Apr-2025 23:36:04.469 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version name: Apache Tomcat/10.1.40
|
|
||||||
21-Apr-2025 23:36:04.476 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server built: Apr 1 2025 17:20:53 UTC
|
|
||||||
21-Apr-2025 23:36:04.476 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version number: 10.1.40.0
|
|
||||||
21-Apr-2025 23:36:04.476 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Name: Linux
|
|
||||||
21-Apr-2025 23:36:04.476 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Version: 5.15.167.4-microsoft-standard-WSL2
|
|
||||||
21-Apr-2025 23:36:04.476 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Architecture: amd64
|
|
||||||
21-Apr-2025 23:36:04.477 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Java Home: /opt/java/openjdk
|
|
||||||
21-Apr-2025 23:36:04.477 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Version: 17.0.14+7
|
|
||||||
21-Apr-2025 23:36:04.477 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Vendor: Eclipse Adoptium
|
|
||||||
21-Apr-2025 23:36:04.477 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_BASE: /usr/local/tomcat
|
|
||||||
21-Apr-2025 23:36:04.477 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_HOME: /usr/local/tomcat
|
|
||||||
21-Apr-2025 23:36:04.503 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties
|
|
||||||
21-Apr-2025 23:36:04.503 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
|
|
||||||
21-Apr-2025 23:36:04.504 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djdk.tls.ephemeralDHKeySize=2048
|
|
||||||
21-Apr-2025 23:36:04.504 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.protocol.handler.pkgs=org.apache.catalina.webresources
|
|
||||||
21-Apr-2025 23:36:04.504 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dsun.io.useCanonCaches=false
|
|
||||||
21-Apr-2025 23:36:04.504 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dorg.apache.catalina.security.SecurityListener.UMASK=0027
|
|
||||||
21-Apr-2025 23:36:04.504 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.lang=ALL-UNNAMED
|
|
||||||
21-Apr-2025 23:36:04.505 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.lang.reflect=ALL-UNNAMED
|
|
||||||
21-Apr-2025 23:36:04.505 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.io=ALL-UNNAMED
|
|
||||||
21-Apr-2025 23:36:04.505 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.util=ALL-UNNAMED
|
|
||||||
21-Apr-2025 23:36:04.506 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.util.concurrent=ALL-UNNAMED
|
|
||||||
21-Apr-2025 23:36:04.506 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED
|
|
||||||
21-Apr-2025 23:36:04.506 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcatalina.base=/usr/local/tomcat
|
|
||||||
21-Apr-2025 23:36:04.506 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcatalina.home=/usr/local/tomcat
|
|
||||||
21-Apr-2025 23:36:04.506 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.io.tmpdir=/usr/local/tomcat/temp
|
|
||||||
21-Apr-2025 23:36:04.519 INFO [main] org.apache.catalina.core.AprLifecycleListener.lifecycleEvent Loaded Apache Tomcat Native library [2.0.8] using APR version [1.7.2].
|
|
||||||
21-Apr-2025 23:36:04.524 INFO [main] org.apache.catalina.core.AprLifecycleListener.initializeSSL OpenSSL successfully initialized [OpenSSL 3.0.13 30 Jan 2024]
|
|
||||||
21-Apr-2025 23:36:05.240 INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["http-nio-8080"]
|
|
||||||
21-Apr-2025 23:36:05.313 INFO [main] org.apache.catalina.startup.Catalina.load Server initialization in [1239] milliseconds
|
|
||||||
21-Apr-2025 23:36:05.489 INFO [main] org.apache.catalina.core.StandardService.startInternal Starting service [Catalina]
|
|
||||||
21-Apr-2025 23:36:05.490 INFO [main] org.apache.catalina.core.StandardEngine.startInternal Starting Servlet engine: [Apache Tomcat/10.1.40]
|
|
||||||
21-Apr-2025 23:36:05.551 INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deploying web application archive [/usr/local/tomcat/webapps/ROOT.war]
|
|
||||||
21-Apr-2025 23:36:05.590 SEVERE [main] org.apache.catalina.startup.ContextConfig.beforeStart Exception fixing docBase for context []
|
|
||||||
java.util.zip.ZipException: zip END header not found
|
|
||||||
at java.base/java.util.zip.ZipFile$Source.findEND(ZipFile.java:1637)
|
|
||||||
at java.base/java.util.zip.ZipFile$Source.initCEN(ZipFile.java:1645)
|
|
||||||
at java.base/java.util.zip.ZipFile$Source.<init>(ZipFile.java:1483)
|
|
||||||
at java.base/java.util.zip.ZipFile$Source.get(ZipFile.java:1445)
|
|
||||||
at java.base/java.util.zip.ZipFile$CleanableResource.<init>(ZipFile.java:717)
|
|
||||||
at java.base/java.util.zip.ZipFile.<init>(ZipFile.java:251)
|
|
||||||
at java.base/java.util.zip.ZipFile.<init>(ZipFile.java:180)
|
|
||||||
at java.base/java.util.jar.JarFile.<init>(JarFile.java:346)
|
|
||||||
at java.base/sun.net.www.protocol.jar.URLJarFile.<init>(URLJarFile.java:103)
|
|
||||||
at java.base/sun.net.www.protocol.jar.URLJarFile.getJarFile(URLJarFile.java:72)
|
|
||||||
at java.base/sun.net.www.protocol.jar.JarFileFactory.get(JarFileFactory.java:168)
|
|
||||||
at java.base/sun.net.www.protocol.jar.JarURLConnection.connect(JarURLConnection.java:131)
|
|
||||||
at java.base/sun.net.www.protocol.jar.JarURLConnection.getJarFile(JarURLConnection.java:92)
|
|
||||||
at org.apache.catalina.startup.ExpandWar.expand(ExpandWar.java:123)
|
|
||||||
at org.apache.catalina.startup.ContextConfig.fixDocBase(ContextConfig.java:812)
|
|
||||||
at org.apache.catalina.startup.ContextConfig.beforeStart(ContextConfig.java:948)
|
|
||||||
at org.apache.catalina.startup.ContextConfig.lifecycleEvent(ContextConfig.java:292)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:109)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.setStateInternal(LifecycleBase.java:389)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:163)
|
|
||||||
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:599)
|
|
||||||
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:571)
|
|
||||||
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:654)
|
|
||||||
at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:964)
|
|
||||||
at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1902)
|
|
||||||
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)
|
|
||||||
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
|
|
||||||
at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
|
|
||||||
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:123)
|
|
||||||
at org.apache.catalina.startup.HostConfig.deployWARs(HostConfig.java:769)
|
|
||||||
at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:420)
|
|
||||||
at org.apache.catalina.startup.HostConfig.start(HostConfig.java:1620)
|
|
||||||
at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:303)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:109)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.setStateInternal(LifecycleBase.java:389)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.setState(LifecycleBase.java:336)
|
|
||||||
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:776)
|
|
||||||
at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:772)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1203)
|
|
||||||
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1193)
|
|
||||||
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
|
|
||||||
at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
|
|
||||||
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:145)
|
|
||||||
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:749)
|
|
||||||
at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:203)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.core.StandardService.startInternal(StandardService.java:412)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:870)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.startup.Catalina.start(Catalina.java:761)
|
|
||||||
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
|
|
||||||
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
|
|
||||||
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
|
|
||||||
at java.base/java.lang.reflect.Method.invoke(Method.java:569)
|
|
||||||
at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:345)
|
|
||||||
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:476)
|
|
||||||
21-Apr-2025 23:36:05.657 SEVERE [main] org.apache.catalina.startup.HostConfig.deployWAR Error deploying web application archive [/usr/local/tomcat/webapps/ROOT.war]
|
|
||||||
java.lang.IllegalStateException: Error starting child
|
|
||||||
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:602)
|
|
||||||
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:571)
|
|
||||||
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:654)
|
|
||||||
at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:964)
|
|
||||||
at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1902)
|
|
||||||
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)
|
|
||||||
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
|
|
||||||
at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
|
|
||||||
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:123)
|
|
||||||
at org.apache.catalina.startup.HostConfig.deployWARs(HostConfig.java:769)
|
|
||||||
at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:420)
|
|
||||||
at org.apache.catalina.startup.HostConfig.start(HostConfig.java:1620)
|
|
||||||
at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:303)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:109)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.setStateInternal(LifecycleBase.java:389)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.setState(LifecycleBase.java:336)
|
|
||||||
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:776)
|
|
||||||
at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:772)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1203)
|
|
||||||
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1193)
|
|
||||||
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
|
|
||||||
at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
|
|
||||||
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:145)
|
|
||||||
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:749)
|
|
||||||
at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:203)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.core.StandardService.startInternal(StandardService.java:412)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:870)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.startup.Catalina.start(Catalina.java:761)
|
|
||||||
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
|
|
||||||
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
|
|
||||||
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
|
|
||||||
at java.base/java.lang.reflect.Method.invoke(Method.java:569)
|
|
||||||
at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:345)
|
|
||||||
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:476)
|
|
||||||
Caused by: org.apache.catalina.LifecycleException: Failed to initialize component [org.apache.catalina.webresources.WarResourceSet@d554c5f]
|
|
||||||
at org.apache.catalina.util.LifecycleBase.handleSubClassException(LifecycleBase.java:406)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:125)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:155)
|
|
||||||
at org.apache.catalina.webresources.StandardRoot.startInternal(StandardRoot.java:723)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.core.StandardContext.resourcesStart(StandardContext.java:4159)
|
|
||||||
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:4281)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:599)
|
|
||||||
... 37 more
|
|
||||||
Caused by: java.lang.IllegalArgumentException: java.util.zip.ZipException: zip END header not found
|
|
||||||
at org.apache.catalina.webresources.AbstractSingleArchiveResourceSet.initInternal(AbstractSingleArchiveResourceSet.java:141)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:122)
|
|
||||||
... 44 more
|
|
||||||
Caused by: java.util.zip.ZipException: zip END header not found
|
|
||||||
at java.base/java.util.zip.ZipFile$Source.findEND(ZipFile.java:1637)
|
|
||||||
at java.base/java.util.zip.ZipFile$Source.initCEN(ZipFile.java:1645)
|
|
||||||
at java.base/java.util.zip.ZipFile$Source.<init>(ZipFile.java:1483)
|
|
||||||
at java.base/java.util.zip.ZipFile$Source.get(ZipFile.java:1445)
|
|
||||||
at java.base/java.util.zip.ZipFile$CleanableResource.<init>(ZipFile.java:717)
|
|
||||||
at java.base/java.util.zip.ZipFile.<init>(ZipFile.java:251)
|
|
||||||
at java.base/java.util.zip.ZipFile.<init>(ZipFile.java:180)
|
|
||||||
at java.base/java.util.jar.JarFile.<init>(JarFile.java:346)
|
|
||||||
at org.apache.catalina.webresources.AbstractSingleArchiveResourceSet.initInternal(AbstractSingleArchiveResourceSet.java:138)
|
|
||||||
... 45 more
|
|
||||||
21-Apr-2025 23:36:05.660 INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deployment of web application archive [/usr/local/tomcat/webapps/ROOT.war] has finished in [108] ms
|
|
||||||
21-Apr-2025 23:36:05.662 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [/usr/local/tomcat/webapps/custom]
|
|
||||||
21-Apr-2025 23:36:06.431 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/usr/local/tomcat/webapps/custom] has finished in [769] ms
|
|
||||||
21-Apr-2025 23:36:06.432 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [/usr/local/tomcat/webapps/app]
|
|
||||||
21-Apr-2025 23:36:06.468 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/usr/local/tomcat/webapps/app] has finished in [36] ms
|
|
||||||
21-Apr-2025 23:36:06.478 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080"]
|
|
||||||
21-Apr-2025 23:36:06.561 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [1247] milliseconds
|
|
||||||
21-Apr-2025 23:51:48.137 INFO [Thread-1] org.apache.coyote.AbstractProtocol.pause Pausing ProtocolHandler ["http-nio-8080"]
|
|
||||||
21-Apr-2025 23:51:48.297 INFO [Thread-1] org.apache.catalina.core.StandardService.stopInternal Stopping service [Catalina]
|
|
||||||
21-Apr-2025 23:51:48.716 INFO [Thread-1] org.apache.coyote.AbstractProtocol.stop Stopping ProtocolHandler ["http-nio-8080"]
|
|
||||||
21-Apr-2025 23:51:48.787 INFO [Thread-1] org.apache.coyote.AbstractProtocol.destroy Destroying ProtocolHandler ["http-nio-8080"]
|
|
||||||
22-Apr-2025 00:09:47.747 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version name: Apache Tomcat/10.1.40
|
|
||||||
22-Apr-2025 00:09:47.763 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server built: Apr 1 2025 17:20:53 UTC
|
|
||||||
22-Apr-2025 00:09:47.763 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version number: 10.1.40.0
|
|
||||||
22-Apr-2025 00:09:47.763 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Name: Linux
|
|
||||||
22-Apr-2025 00:09:47.764 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Version: 5.15.167.4-microsoft-standard-WSL2
|
|
||||||
22-Apr-2025 00:09:47.764 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Architecture: amd64
|
|
||||||
22-Apr-2025 00:09:47.764 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Java Home: /opt/java/openjdk
|
|
||||||
22-Apr-2025 00:09:47.764 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Version: 17.0.14+7
|
|
||||||
22-Apr-2025 00:09:47.764 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Vendor: Eclipse Adoptium
|
|
||||||
22-Apr-2025 00:09:47.765 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_BASE: /usr/local/tomcat
|
|
||||||
22-Apr-2025 00:09:47.765 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_HOME: /usr/local/tomcat
|
|
||||||
22-Apr-2025 00:09:47.796 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties
|
|
||||||
22-Apr-2025 00:09:47.796 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
|
|
||||||
22-Apr-2025 00:09:47.797 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djdk.tls.ephemeralDHKeySize=2048
|
|
||||||
22-Apr-2025 00:09:47.797 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.protocol.handler.pkgs=org.apache.catalina.webresources
|
|
||||||
22-Apr-2025 00:09:47.797 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dsun.io.useCanonCaches=false
|
|
||||||
22-Apr-2025 00:09:47.797 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dorg.apache.catalina.security.SecurityListener.UMASK=0027
|
|
||||||
22-Apr-2025 00:09:47.797 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.lang=ALL-UNNAMED
|
|
||||||
22-Apr-2025 00:09:47.797 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.lang.reflect=ALL-UNNAMED
|
|
||||||
22-Apr-2025 00:09:47.797 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.io=ALL-UNNAMED
|
|
||||||
22-Apr-2025 00:09:47.798 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.util=ALL-UNNAMED
|
|
||||||
22-Apr-2025 00:09:47.798 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.util.concurrent=ALL-UNNAMED
|
|
||||||
22-Apr-2025 00:09:47.798 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED
|
|
||||||
22-Apr-2025 00:09:47.798 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcatalina.base=/usr/local/tomcat
|
|
||||||
22-Apr-2025 00:09:47.798 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcatalina.home=/usr/local/tomcat
|
|
||||||
22-Apr-2025 00:09:47.798 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.io.tmpdir=/usr/local/tomcat/temp
|
|
||||||
22-Apr-2025 00:09:47.815 INFO [main] org.apache.catalina.core.AprLifecycleListener.lifecycleEvent Loaded Apache Tomcat Native library [2.0.8] using APR version [1.7.2].
|
|
||||||
22-Apr-2025 00:09:47.822 INFO [main] org.apache.catalina.core.AprLifecycleListener.initializeSSL OpenSSL successfully initialized [OpenSSL 3.0.13 30 Jan 2024]
|
|
||||||
22-Apr-2025 00:09:48.310 INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["http-nio-8080"]
|
|
||||||
22-Apr-2025 00:09:48.348 INFO [main] org.apache.catalina.startup.Catalina.load Server initialization in [997] milliseconds
|
|
||||||
22-Apr-2025 00:09:48.467 INFO [main] org.apache.catalina.core.StandardService.startInternal Starting service [Catalina]
|
|
||||||
22-Apr-2025 00:09:48.467 INFO [main] org.apache.catalina.core.StandardEngine.startInternal Starting Servlet engine: [Apache Tomcat/10.1.40]
|
|
||||||
22-Apr-2025 00:09:48.489 INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deploying web application archive [/usr/local/tomcat/webapps/ROOT.war]
|
|
||||||
22-Apr-2025 00:09:48.519 SEVERE [main] org.apache.catalina.startup.ContextConfig.beforeStart Exception fixing docBase for context []
|
|
||||||
java.util.zip.ZipException: zip END header not found
|
|
||||||
at java.base/java.util.zip.ZipFile$Source.findEND(ZipFile.java:1637)
|
|
||||||
at java.base/java.util.zip.ZipFile$Source.initCEN(ZipFile.java:1645)
|
|
||||||
at java.base/java.util.zip.ZipFile$Source.<init>(ZipFile.java:1483)
|
|
||||||
at java.base/java.util.zip.ZipFile$Source.get(ZipFile.java:1445)
|
|
||||||
at java.base/java.util.zip.ZipFile$CleanableResource.<init>(ZipFile.java:717)
|
|
||||||
at java.base/java.util.zip.ZipFile.<init>(ZipFile.java:251)
|
|
||||||
at java.base/java.util.zip.ZipFile.<init>(ZipFile.java:180)
|
|
||||||
at java.base/java.util.jar.JarFile.<init>(JarFile.java:346)
|
|
||||||
at java.base/sun.net.www.protocol.jar.URLJarFile.<init>(URLJarFile.java:103)
|
|
||||||
at java.base/sun.net.www.protocol.jar.URLJarFile.getJarFile(URLJarFile.java:72)
|
|
||||||
at java.base/sun.net.www.protocol.jar.JarFileFactory.get(JarFileFactory.java:168)
|
|
||||||
at java.base/sun.net.www.protocol.jar.JarURLConnection.connect(JarURLConnection.java:131)
|
|
||||||
at java.base/sun.net.www.protocol.jar.JarURLConnection.getJarFile(JarURLConnection.java:92)
|
|
||||||
at org.apache.catalina.startup.ExpandWar.expand(ExpandWar.java:123)
|
|
||||||
at org.apache.catalina.startup.ContextConfig.fixDocBase(ContextConfig.java:812)
|
|
||||||
at org.apache.catalina.startup.ContextConfig.beforeStart(ContextConfig.java:948)
|
|
||||||
at org.apache.catalina.startup.ContextConfig.lifecycleEvent(ContextConfig.java:292)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:109)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.setStateInternal(LifecycleBase.java:389)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:163)
|
|
||||||
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:599)
|
|
||||||
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:571)
|
|
||||||
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:654)
|
|
||||||
at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:964)
|
|
||||||
at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1902)
|
|
||||||
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)
|
|
||||||
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
|
|
||||||
at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
|
|
||||||
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:123)
|
|
||||||
at org.apache.catalina.startup.HostConfig.deployWARs(HostConfig.java:769)
|
|
||||||
at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:420)
|
|
||||||
at org.apache.catalina.startup.HostConfig.start(HostConfig.java:1620)
|
|
||||||
at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:303)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:109)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.setStateInternal(LifecycleBase.java:389)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.setState(LifecycleBase.java:336)
|
|
||||||
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:776)
|
|
||||||
at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:772)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1203)
|
|
||||||
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1193)
|
|
||||||
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
|
|
||||||
at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
|
|
||||||
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:145)
|
|
||||||
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:749)
|
|
||||||
at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:203)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.core.StandardService.startInternal(StandardService.java:412)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:870)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.startup.Catalina.start(Catalina.java:761)
|
|
||||||
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
|
|
||||||
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
|
|
||||||
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
|
|
||||||
at java.base/java.lang.reflect.Method.invoke(Method.java:569)
|
|
||||||
at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:345)
|
|
||||||
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:476)
|
|
||||||
22-Apr-2025 00:09:48.556 SEVERE [main] org.apache.catalina.startup.HostConfig.deployWAR Error deploying web application archive [/usr/local/tomcat/webapps/ROOT.war]
|
|
||||||
java.lang.IllegalStateException: Error starting child
|
|
||||||
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:602)
|
|
||||||
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:571)
|
|
||||||
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:654)
|
|
||||||
at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:964)
|
|
||||||
at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1902)
|
|
||||||
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)
|
|
||||||
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
|
|
||||||
at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
|
|
||||||
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:123)
|
|
||||||
at org.apache.catalina.startup.HostConfig.deployWARs(HostConfig.java:769)
|
|
||||||
at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:420)
|
|
||||||
at org.apache.catalina.startup.HostConfig.start(HostConfig.java:1620)
|
|
||||||
at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:303)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:109)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.setStateInternal(LifecycleBase.java:389)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.setState(LifecycleBase.java:336)
|
|
||||||
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:776)
|
|
||||||
at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:772)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1203)
|
|
||||||
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1193)
|
|
||||||
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
|
|
||||||
at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
|
|
||||||
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:145)
|
|
||||||
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:749)
|
|
||||||
at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:203)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.core.StandardService.startInternal(StandardService.java:412)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:870)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.startup.Catalina.start(Catalina.java:761)
|
|
||||||
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
|
|
||||||
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
|
|
||||||
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
|
|
||||||
at java.base/java.lang.reflect.Method.invoke(Method.java:569)
|
|
||||||
at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:345)
|
|
||||||
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:476)
|
|
||||||
Caused by: org.apache.catalina.LifecycleException: Failed to initialize component [org.apache.catalina.webresources.WarResourceSet@68c9d179]
|
|
||||||
at org.apache.catalina.util.LifecycleBase.handleSubClassException(LifecycleBase.java:406)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:125)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:155)
|
|
||||||
at org.apache.catalina.webresources.StandardRoot.startInternal(StandardRoot.java:723)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.core.StandardContext.resourcesStart(StandardContext.java:4159)
|
|
||||||
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:4281)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:599)
|
|
||||||
... 37 more
|
|
||||||
Caused by: java.lang.IllegalArgumentException: java.util.zip.ZipException: zip END header not found
|
|
||||||
at org.apache.catalina.webresources.AbstractSingleArchiveResourceSet.initInternal(AbstractSingleArchiveResourceSet.java:141)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:122)
|
|
||||||
... 44 more
|
|
||||||
Caused by: java.util.zip.ZipException: zip END header not found
|
|
||||||
at java.base/java.util.zip.ZipFile$Source.findEND(ZipFile.java:1637)
|
|
||||||
at java.base/java.util.zip.ZipFile$Source.initCEN(ZipFile.java:1645)
|
|
||||||
at java.base/java.util.zip.ZipFile$Source.<init>(ZipFile.java:1483)
|
|
||||||
at java.base/java.util.zip.ZipFile$Source.get(ZipFile.java:1445)
|
|
||||||
at java.base/java.util.zip.ZipFile$CleanableResource.<init>(ZipFile.java:717)
|
|
||||||
at java.base/java.util.zip.ZipFile.<init>(ZipFile.java:251)
|
|
||||||
at java.base/java.util.zip.ZipFile.<init>(ZipFile.java:180)
|
|
||||||
at java.base/java.util.jar.JarFile.<init>(JarFile.java:346)
|
|
||||||
at org.apache.catalina.webresources.AbstractSingleArchiveResourceSet.initInternal(AbstractSingleArchiveResourceSet.java:138)
|
|
||||||
... 45 more
|
|
||||||
22-Apr-2025 00:09:48.560 INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deployment of web application archive [/usr/local/tomcat/webapps/ROOT.war] has finished in [69] ms
|
|
||||||
22-Apr-2025 00:09:48.561 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [/usr/local/tomcat/webapps/custom]
|
|
||||||
22-Apr-2025 00:09:49.063 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/usr/local/tomcat/webapps/custom] has finished in [502] ms
|
|
||||||
22-Apr-2025 00:09:49.063 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [/usr/local/tomcat/webapps/app]
|
|
||||||
22-Apr-2025 00:09:49.084 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/usr/local/tomcat/webapps/app] has finished in [21] ms
|
|
||||||
22-Apr-2025 00:09:49.103 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080"]
|
|
||||||
22-Apr-2025 00:09:49.166 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [817] milliseconds
|
|
||||||
22-Apr-2025 00:25:04.510 INFO [Thread-1] org.apache.coyote.AbstractProtocol.pause Pausing ProtocolHandler ["http-nio-8080"]
|
|
||||||
22-Apr-2025 00:25:04.547 INFO [Thread-1] org.apache.catalina.core.StandardService.stopInternal Stopping service [Catalina]
|
|
||||||
22-Apr-2025 00:25:04.657 INFO [Thread-1] org.apache.coyote.AbstractProtocol.stop Stopping ProtocolHandler ["http-nio-8080"]
|
|
||||||
22-Apr-2025 00:25:04.685 INFO [Thread-1] org.apache.coyote.AbstractProtocol.destroy Destroying ProtocolHandler ["http-nio-8080"]
|
|
||||||
22-Apr-2025 00:27:26.457 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version name: Apache Tomcat/10.1.40
|
|
||||||
22-Apr-2025 00:27:26.467 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server built: Apr 1 2025 17:20:53 UTC
|
|
||||||
22-Apr-2025 00:27:26.467 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version number: 10.1.40.0
|
|
||||||
22-Apr-2025 00:27:26.467 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Name: Linux
|
|
||||||
22-Apr-2025 00:27:26.467 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Version: 5.15.167.4-microsoft-standard-WSL2
|
|
||||||
22-Apr-2025 00:27:26.467 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Architecture: amd64
|
|
||||||
22-Apr-2025 00:27:26.468 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Java Home: /opt/java/openjdk
|
|
||||||
22-Apr-2025 00:27:26.468 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Version: 17.0.14+7
|
|
||||||
22-Apr-2025 00:27:26.468 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Vendor: Eclipse Adoptium
|
|
||||||
22-Apr-2025 00:27:26.468 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_BASE: /usr/local/tomcat
|
|
||||||
22-Apr-2025 00:27:26.468 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_HOME: /usr/local/tomcat
|
|
||||||
22-Apr-2025 00:27:26.492 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties
|
|
||||||
22-Apr-2025 00:27:26.492 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
|
|
||||||
22-Apr-2025 00:27:26.492 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djdk.tls.ephemeralDHKeySize=2048
|
|
||||||
22-Apr-2025 00:27:26.492 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.protocol.handler.pkgs=org.apache.catalina.webresources
|
|
||||||
22-Apr-2025 00:27:26.492 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dsun.io.useCanonCaches=false
|
|
||||||
22-Apr-2025 00:27:26.492 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dorg.apache.catalina.security.SecurityListener.UMASK=0027
|
|
||||||
22-Apr-2025 00:27:26.492 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.lang=ALL-UNNAMED
|
|
||||||
22-Apr-2025 00:27:26.492 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.lang.reflect=ALL-UNNAMED
|
|
||||||
22-Apr-2025 00:27:26.493 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.io=ALL-UNNAMED
|
|
||||||
22-Apr-2025 00:27:26.493 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.util=ALL-UNNAMED
|
|
||||||
22-Apr-2025 00:27:26.493 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.util.concurrent=ALL-UNNAMED
|
|
||||||
22-Apr-2025 00:27:26.493 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED
|
|
||||||
22-Apr-2025 00:27:26.494 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcatalina.base=/usr/local/tomcat
|
|
||||||
22-Apr-2025 00:27:26.494 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcatalina.home=/usr/local/tomcat
|
|
||||||
22-Apr-2025 00:27:26.494 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.io.tmpdir=/usr/local/tomcat/temp
|
|
||||||
22-Apr-2025 00:27:26.501 INFO [main] org.apache.catalina.core.AprLifecycleListener.lifecycleEvent Loaded Apache Tomcat Native library [2.0.8] using APR version [1.7.2].
|
|
||||||
22-Apr-2025 00:27:26.505 INFO [main] org.apache.catalina.core.AprLifecycleListener.initializeSSL OpenSSL successfully initialized [OpenSSL 3.0.13 30 Jan 2024]
|
|
||||||
22-Apr-2025 00:27:26.893 INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["http-nio-8080"]
|
|
||||||
22-Apr-2025 00:27:26.937 INFO [main] org.apache.catalina.startup.Catalina.load Server initialization in [790] milliseconds
|
|
||||||
22-Apr-2025 00:27:27.009 INFO [main] org.apache.catalina.core.StandardService.startInternal Starting service [Catalina]
|
|
||||||
22-Apr-2025 00:27:27.009 INFO [main] org.apache.catalina.core.StandardEngine.startInternal Starting Servlet engine: [Apache Tomcat/10.1.40]
|
|
||||||
22-Apr-2025 00:27:27.033 INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deploying web application archive [/usr/local/tomcat/webapps/ROOT.war]
|
|
||||||
22-Apr-2025 00:27:27.058 SEVERE [main] org.apache.catalina.startup.ContextConfig.beforeStart Exception fixing docBase for context []
|
|
||||||
java.util.zip.ZipException: zip END header not found
|
|
||||||
at java.base/java.util.zip.ZipFile$Source.findEND(ZipFile.java:1637)
|
|
||||||
at java.base/java.util.zip.ZipFile$Source.initCEN(ZipFile.java:1645)
|
|
||||||
at java.base/java.util.zip.ZipFile$Source.<init>(ZipFile.java:1483)
|
|
||||||
at java.base/java.util.zip.ZipFile$Source.get(ZipFile.java:1445)
|
|
||||||
at java.base/java.util.zip.ZipFile$CleanableResource.<init>(ZipFile.java:717)
|
|
||||||
at java.base/java.util.zip.ZipFile.<init>(ZipFile.java:251)
|
|
||||||
at java.base/java.util.zip.ZipFile.<init>(ZipFile.java:180)
|
|
||||||
at java.base/java.util.jar.JarFile.<init>(JarFile.java:346)
|
|
||||||
at java.base/sun.net.www.protocol.jar.URLJarFile.<init>(URLJarFile.java:103)
|
|
||||||
at java.base/sun.net.www.protocol.jar.URLJarFile.getJarFile(URLJarFile.java:72)
|
|
||||||
at java.base/sun.net.www.protocol.jar.JarFileFactory.get(JarFileFactory.java:168)
|
|
||||||
at java.base/sun.net.www.protocol.jar.JarURLConnection.connect(JarURLConnection.java:131)
|
|
||||||
at java.base/sun.net.www.protocol.jar.JarURLConnection.getJarFile(JarURLConnection.java:92)
|
|
||||||
at org.apache.catalina.startup.ExpandWar.expand(ExpandWar.java:123)
|
|
||||||
at org.apache.catalina.startup.ContextConfig.fixDocBase(ContextConfig.java:812)
|
|
||||||
at org.apache.catalina.startup.ContextConfig.beforeStart(ContextConfig.java:948)
|
|
||||||
at org.apache.catalina.startup.ContextConfig.lifecycleEvent(ContextConfig.java:292)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:109)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.setStateInternal(LifecycleBase.java:389)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:163)
|
|
||||||
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:599)
|
|
||||||
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:571)
|
|
||||||
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:654)
|
|
||||||
at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:964)
|
|
||||||
at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1902)
|
|
||||||
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)
|
|
||||||
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
|
|
||||||
at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
|
|
||||||
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:123)
|
|
||||||
at org.apache.catalina.startup.HostConfig.deployWARs(HostConfig.java:769)
|
|
||||||
at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:420)
|
|
||||||
at org.apache.catalina.startup.HostConfig.start(HostConfig.java:1620)
|
|
||||||
at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:303)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:109)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.setStateInternal(LifecycleBase.java:389)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.setState(LifecycleBase.java:336)
|
|
||||||
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:776)
|
|
||||||
at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:772)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1203)
|
|
||||||
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1193)
|
|
||||||
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
|
|
||||||
at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
|
|
||||||
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:145)
|
|
||||||
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:749)
|
|
||||||
at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:203)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.core.StandardService.startInternal(StandardService.java:412)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:870)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.startup.Catalina.start(Catalina.java:761)
|
|
||||||
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
|
|
||||||
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
|
|
||||||
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
|
|
||||||
at java.base/java.lang.reflect.Method.invoke(Method.java:569)
|
|
||||||
at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:345)
|
|
||||||
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:476)
|
|
||||||
22-Apr-2025 00:27:27.096 SEVERE [main] org.apache.catalina.startup.HostConfig.deployWAR Error deploying web application archive [/usr/local/tomcat/webapps/ROOT.war]
|
|
||||||
java.lang.IllegalStateException: Error starting child
|
|
||||||
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:602)
|
|
||||||
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:571)
|
|
||||||
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:654)
|
|
||||||
at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:964)
|
|
||||||
at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1902)
|
|
||||||
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)
|
|
||||||
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
|
|
||||||
at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
|
|
||||||
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:123)
|
|
||||||
at org.apache.catalina.startup.HostConfig.deployWARs(HostConfig.java:769)
|
|
||||||
at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:420)
|
|
||||||
at org.apache.catalina.startup.HostConfig.start(HostConfig.java:1620)
|
|
||||||
at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:303)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:109)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.setStateInternal(LifecycleBase.java:389)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.setState(LifecycleBase.java:336)
|
|
||||||
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:776)
|
|
||||||
at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:772)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1203)
|
|
||||||
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1193)
|
|
||||||
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
|
|
||||||
at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
|
|
||||||
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:145)
|
|
||||||
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:749)
|
|
||||||
at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:203)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.core.StandardService.startInternal(StandardService.java:412)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:870)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.startup.Catalina.start(Catalina.java:761)
|
|
||||||
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
|
|
||||||
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
|
|
||||||
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
|
|
||||||
at java.base/java.lang.reflect.Method.invoke(Method.java:569)
|
|
||||||
at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:345)
|
|
||||||
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:476)
|
|
||||||
Caused by: org.apache.catalina.LifecycleException: Failed to initialize component [org.apache.catalina.webresources.WarResourceSet@4b520ea8]
|
|
||||||
at org.apache.catalina.util.LifecycleBase.handleSubClassException(LifecycleBase.java:406)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:125)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:155)
|
|
||||||
at org.apache.catalina.webresources.StandardRoot.startInternal(StandardRoot.java:723)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.core.StandardContext.resourcesStart(StandardContext.java:4159)
|
|
||||||
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:4281)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
|
||||||
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:599)
|
|
||||||
... 37 more
|
|
||||||
Caused by: java.lang.IllegalArgumentException: java.util.zip.ZipException: zip END header not found
|
|
||||||
at org.apache.catalina.webresources.AbstractSingleArchiveResourceSet.initInternal(AbstractSingleArchiveResourceSet.java:141)
|
|
||||||
at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:122)
|
|
||||||
... 44 more
|
|
||||||
Caused by: java.util.zip.ZipException: zip END header not found
|
|
||||||
at java.base/java.util.zip.ZipFile$Source.findEND(ZipFile.java:1637)
|
|
||||||
at java.base/java.util.zip.ZipFile$Source.initCEN(ZipFile.java:1645)
|
|
||||||
at java.base/java.util.zip.ZipFile$Source.<init>(ZipFile.java:1483)
|
|
||||||
at java.base/java.util.zip.ZipFile$Source.get(ZipFile.java:1445)
|
|
||||||
at java.base/java.util.zip.ZipFile$CleanableResource.<init>(ZipFile.java:717)
|
|
||||||
at java.base/java.util.zip.ZipFile.<init>(ZipFile.java:251)
|
|
||||||
at java.base/java.util.zip.ZipFile.<init>(ZipFile.java:180)
|
|
||||||
at java.base/java.util.jar.JarFile.<init>(JarFile.java:346)
|
|
||||||
at org.apache.catalina.webresources.AbstractSingleArchiveResourceSet.initInternal(AbstractSingleArchiveResourceSet.java:138)
|
|
||||||
... 45 more
|
|
||||||
22-Apr-2025 00:27:27.100 INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deployment of web application archive [/usr/local/tomcat/webapps/ROOT.war] has finished in [67] ms
|
|
||||||
22-Apr-2025 00:27:27.101 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [/usr/local/tomcat/webapps/custom]
|
|
||||||
22-Apr-2025 00:27:27.481 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/usr/local/tomcat/webapps/custom] has finished in [379] ms
|
|
||||||
22-Apr-2025 00:27:27.482 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [/usr/local/tomcat/webapps/app]
|
|
||||||
22-Apr-2025 00:27:27.504 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/usr/local/tomcat/webapps/app] has finished in [22] ms
|
|
||||||
22-Apr-2025 00:27:27.510 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080"]
|
|
||||||
22-Apr-2025 00:27:27.530 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [593] milliseconds
|
|
||||||
22-Apr-2025 00:54:34.132 INFO [Thread-1] org.apache.coyote.AbstractProtocol.pause Pausing ProtocolHandler ["http-nio-8080"]
|
|
||||||
22-Apr-2025 00:54:34.299 INFO [Thread-1] org.apache.catalina.core.StandardService.stopInternal Stopping service [Catalina]
|
|
||||||
22-Apr-2025 00:54:34.497 INFO [Thread-1] org.apache.coyote.AbstractProtocol.stop Stopping ProtocolHandler ["http-nio-8080"]
|
|
||||||
22-Apr-2025 00:54:34.519 INFO [Thread-1] org.apache.coyote.AbstractProtocol.destroy Destroying ProtocolHandler ["http-nio-8080"]
|
|
||||||
1
migrations/README
Normal file
1
migrations/README
Normal file
@ -0,0 +1 @@
|
|||||||
|
Single-database configuration for Flask.
|
||||||
BIN
migrations/__pycache__/env.cpython-312.pyc
Normal file
BIN
migrations/__pycache__/env.cpython-312.pyc
Normal file
Binary file not shown.
50
migrations/alembic.ini
Normal file
50
migrations/alembic.ini
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# A generic, single database configuration.
|
||||||
|
|
||||||
|
[alembic]
|
||||||
|
# template used to generate migration files
|
||||||
|
# file_template = %%(rev)s_%%(slug)s
|
||||||
|
|
||||||
|
# set to 'true' to run the environment during
|
||||||
|
# the 'revision' command, regardless of autogenerate
|
||||||
|
# revision_environment = false
|
||||||
|
|
||||||
|
|
||||||
|
# Logging configuration
|
||||||
|
[loggers]
|
||||||
|
keys = root,sqlalchemy,alembic,flask_migrate
|
||||||
|
|
||||||
|
[handlers]
|
||||||
|
keys = console
|
||||||
|
|
||||||
|
[formatters]
|
||||||
|
keys = generic
|
||||||
|
|
||||||
|
[logger_root]
|
||||||
|
level = WARN
|
||||||
|
handlers = console
|
||||||
|
qualname =
|
||||||
|
|
||||||
|
[logger_sqlalchemy]
|
||||||
|
level = WARN
|
||||||
|
handlers =
|
||||||
|
qualname = sqlalchemy.engine
|
||||||
|
|
||||||
|
[logger_alembic]
|
||||||
|
level = INFO
|
||||||
|
handlers =
|
||||||
|
qualname = alembic
|
||||||
|
|
||||||
|
[logger_flask_migrate]
|
||||||
|
level = INFO
|
||||||
|
handlers =
|
||||||
|
qualname = flask_migrate
|
||||||
|
|
||||||
|
[handler_console]
|
||||||
|
class = StreamHandler
|
||||||
|
args = (sys.stderr,)
|
||||||
|
level = NOTSET
|
||||||
|
formatter = generic
|
||||||
|
|
||||||
|
[formatter_generic]
|
||||||
|
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||||
|
datefmt = %H:%M:%S
|
||||||
113
migrations/env.py
Normal file
113
migrations/env.py
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import logging
|
||||||
|
from logging.config import fileConfig
|
||||||
|
|
||||||
|
from flask import current_app
|
||||||
|
|
||||||
|
from alembic import context
|
||||||
|
|
||||||
|
# this is the Alembic Config object, which provides
|
||||||
|
# access to the values within the .ini file in use.
|
||||||
|
config = context.config
|
||||||
|
|
||||||
|
# Interpret the config file for Python logging.
|
||||||
|
# This line sets up loggers basically.
|
||||||
|
fileConfig(config.config_file_name)
|
||||||
|
logger = logging.getLogger('alembic.env')
|
||||||
|
|
||||||
|
|
||||||
|
def get_engine():
|
||||||
|
try:
|
||||||
|
# this works with Flask-SQLAlchemy<3 and Alchemical
|
||||||
|
return current_app.extensions['migrate'].db.get_engine()
|
||||||
|
except (TypeError, AttributeError):
|
||||||
|
# this works with Flask-SQLAlchemy>=3
|
||||||
|
return current_app.extensions['migrate'].db.engine
|
||||||
|
|
||||||
|
|
||||||
|
def get_engine_url():
|
||||||
|
try:
|
||||||
|
return get_engine().url.render_as_string(hide_password=False).replace(
|
||||||
|
'%', '%%')
|
||||||
|
except AttributeError:
|
||||||
|
return str(get_engine().url).replace('%', '%%')
|
||||||
|
|
||||||
|
|
||||||
|
# add your model's MetaData object here
|
||||||
|
# for 'autogenerate' support
|
||||||
|
# from myapp import mymodel
|
||||||
|
# target_metadata = mymodel.Base.metadata
|
||||||
|
config.set_main_option('sqlalchemy.url', get_engine_url())
|
||||||
|
target_db = current_app.extensions['migrate'].db
|
||||||
|
|
||||||
|
# other values from the config, defined by the needs of env.py,
|
||||||
|
# can be acquired:
|
||||||
|
# my_important_option = config.get_main_option("my_important_option")
|
||||||
|
# ... etc.
|
||||||
|
|
||||||
|
|
||||||
|
def get_metadata():
|
||||||
|
if hasattr(target_db, 'metadatas'):
|
||||||
|
return target_db.metadatas[None]
|
||||||
|
return target_db.metadata
|
||||||
|
|
||||||
|
|
||||||
|
def run_migrations_offline():
|
||||||
|
"""Run migrations in 'offline' mode.
|
||||||
|
|
||||||
|
This configures the context with just a URL
|
||||||
|
and not an Engine, though an Engine is acceptable
|
||||||
|
here as well. By skipping the Engine creation
|
||||||
|
we don't even need a DBAPI to be available.
|
||||||
|
|
||||||
|
Calls to context.execute() here emit the given string to the
|
||||||
|
script output.
|
||||||
|
|
||||||
|
"""
|
||||||
|
url = config.get_main_option("sqlalchemy.url")
|
||||||
|
context.configure(
|
||||||
|
url=url, target_metadata=get_metadata(), literal_binds=True
|
||||||
|
)
|
||||||
|
|
||||||
|
with context.begin_transaction():
|
||||||
|
context.run_migrations()
|
||||||
|
|
||||||
|
|
||||||
|
def run_migrations_online():
|
||||||
|
"""Run migrations in 'online' mode.
|
||||||
|
|
||||||
|
In this scenario we need to create an Engine
|
||||||
|
and associate a connection with the context.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# this callback is used to prevent an auto-migration from being generated
|
||||||
|
# when there are no changes to the schema
|
||||||
|
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
|
||||||
|
def process_revision_directives(context, revision, directives):
|
||||||
|
if getattr(config.cmd_opts, 'autogenerate', False):
|
||||||
|
script = directives[0]
|
||||||
|
if script.upgrade_ops.is_empty():
|
||||||
|
directives[:] = []
|
||||||
|
logger.info('No changes in schema detected.')
|
||||||
|
|
||||||
|
conf_args = current_app.extensions['migrate'].configure_args
|
||||||
|
if conf_args.get("process_revision_directives") is None:
|
||||||
|
conf_args["process_revision_directives"] = process_revision_directives
|
||||||
|
|
||||||
|
connectable = get_engine()
|
||||||
|
|
||||||
|
with connectable.connect() as connection:
|
||||||
|
context.configure(
|
||||||
|
connection=connection,
|
||||||
|
target_metadata=get_metadata(),
|
||||||
|
**conf_args
|
||||||
|
)
|
||||||
|
|
||||||
|
with context.begin_transaction():
|
||||||
|
context.run_migrations()
|
||||||
|
|
||||||
|
|
||||||
|
if context.is_offline_mode():
|
||||||
|
run_migrations_offline()
|
||||||
|
else:
|
||||||
|
run_migrations_online()
|
||||||
24
migrations/script.py.mako
Normal file
24
migrations/script.py.mako
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
"""${message}
|
||||||
|
|
||||||
|
Revision ID: ${up_revision}
|
||||||
|
Revises: ${down_revision | comma,n}
|
||||||
|
Create Date: ${create_date}
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
${imports if imports else ""}
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = ${repr(up_revision)}
|
||||||
|
down_revision = ${repr(down_revision)}
|
||||||
|
branch_labels = ${repr(branch_labels)}
|
||||||
|
depends_on = ${repr(depends_on)}
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
${upgrades if upgrades else "pass"}
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
${downgrades if downgrades else "pass"}
|
||||||
123
package.py
Normal file
123
package.py
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
from flask import Blueprint, jsonify, current_app, render_template
|
||||||
|
import os
|
||||||
|
import tarfile
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
import time
|
||||||
|
from services import pkg_version, safe_parse_version
|
||||||
|
|
||||||
|
package_bp = Blueprint('package', __name__)
|
||||||
|
|
||||||
|
@package_bp.route('/logs/<name>')
|
||||||
|
def logs(name):
|
||||||
|
"""
|
||||||
|
Fetch logs for a package, listing each version with its publication date.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): The name of the package.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Rendered template with logs or an error message.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
in_memory_cache = current_app.config.get('MANUAL_PACKAGE_CACHE', [])
|
||||||
|
if not in_memory_cache:
|
||||||
|
current_app.logger.error(f"No in-memory cache found for package logs: {name}")
|
||||||
|
return "<p class='text-muted'>Package cache not found.</p>"
|
||||||
|
|
||||||
|
package_data = next((pkg for pkg in in_memory_cache if isinstance(pkg, dict) and pkg.get('name', '').lower() == name.lower()), None)
|
||||||
|
if not package_data:
|
||||||
|
current_app.logger.error(f"Package not found in cache: {name}")
|
||||||
|
return "<p class='text-muted'>Package not found.</p>"
|
||||||
|
|
||||||
|
# Get the versions list with pubDate
|
||||||
|
versions = package_data.get('all_versions', [])
|
||||||
|
if not versions:
|
||||||
|
current_app.logger.warning(f"No versions found for package: {name}. Package data: {package_data}")
|
||||||
|
return "<p class='text-muted'>No version history found for this package.</p>"
|
||||||
|
|
||||||
|
current_app.logger.debug(f"Found {len(versions)} versions for package {name}: {versions[:5]}...")
|
||||||
|
|
||||||
|
logs = []
|
||||||
|
now = time.time()
|
||||||
|
for version_info in versions:
|
||||||
|
if not isinstance(version_info, dict):
|
||||||
|
current_app.logger.warning(f"Invalid version info for {name}: {version_info}")
|
||||||
|
continue
|
||||||
|
version = version_info.get('version', '')
|
||||||
|
pub_date_str = version_info.get('pubDate', '')
|
||||||
|
if not version or not pub_date_str:
|
||||||
|
current_app.logger.warning(f"Skipping version info with missing version or pubDate: {version_info}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Parse pubDate and calculate "when"
|
||||||
|
when = "Unknown"
|
||||||
|
try:
|
||||||
|
pub_date = datetime.strptime(pub_date_str, "%a, %d %b %Y %H:%M:%S %Z")
|
||||||
|
pub_time = pub_date.timestamp()
|
||||||
|
time_diff = now - pub_time
|
||||||
|
days_ago = int(time_diff / 86400)
|
||||||
|
if days_ago < 1:
|
||||||
|
hours_ago = int(time_diff / 3600)
|
||||||
|
if hours_ago < 1:
|
||||||
|
minutes_ago = int(time_diff / 60)
|
||||||
|
when = f"{minutes_ago} minute{'s' if minutes_ago != 1 else ''} ago"
|
||||||
|
else:
|
||||||
|
when = f"{hours_ago} hour{'s' if hours_ago != 1 else ''} ago"
|
||||||
|
else:
|
||||||
|
when = f"{days_ago} day{'s' if days_ago != 1 else ''} ago"
|
||||||
|
except ValueError as e:
|
||||||
|
current_app.logger.warning(f"Failed to parse pubDate '{pub_date_str}' for version {version}: {e}")
|
||||||
|
|
||||||
|
logs.append({
|
||||||
|
"version": version,
|
||||||
|
"pubDate": pub_date_str,
|
||||||
|
"when": when
|
||||||
|
})
|
||||||
|
|
||||||
|
if not logs:
|
||||||
|
current_app.logger.warning(f"No valid version entries with pubDate for package: {name}")
|
||||||
|
return "<p class='text-muted'>No version history found for this package.</p>"
|
||||||
|
|
||||||
|
# Sort logs by version number (newest first)
|
||||||
|
logs.sort(key=lambda x: safe_parse_version(x.get('version', '0.0.0a0')), reverse=True)
|
||||||
|
|
||||||
|
current_app.logger.debug(f"Rendering logs for {name} with {len(logs)} entries")
|
||||||
|
return render_template('package.logs.html', logs=logs)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.error(f"Error in logs endpoint for {name}: {str(e)}", exc_info=True)
|
||||||
|
return "<p class='text-danger'>Error loading version history.</p>", 500
|
||||||
|
|
||||||
|
@package_bp.route('/dependents/<name>')
|
||||||
|
def dependents(name):
|
||||||
|
"""
|
||||||
|
HTMX endpoint to fetch packages that depend on the current package.
|
||||||
|
Returns an HTML fragment with a table of dependent packages.
|
||||||
|
"""
|
||||||
|
in_memory_cache = current_app.config.get('MANUAL_PACKAGE_CACHE', [])
|
||||||
|
package_data = next((pkg for pkg in in_memory_cache if isinstance(pkg, dict) and pkg.get('name', '').lower() == name.lower()), None)
|
||||||
|
|
||||||
|
if not package_data:
|
||||||
|
return "<p class='text-danger'>Package not found.</p>"
|
||||||
|
|
||||||
|
# Find dependents: packages whose dependencies include the current package
|
||||||
|
dependents = []
|
||||||
|
for pkg in in_memory_cache:
|
||||||
|
if not isinstance(pkg, dict):
|
||||||
|
continue
|
||||||
|
dependencies = pkg.get('dependencies', [])
|
||||||
|
for dep in dependencies:
|
||||||
|
dep_name = dep.get('name', '')
|
||||||
|
if dep_name.lower() == name.lower():
|
||||||
|
dependents.append({
|
||||||
|
"name": pkg.get('name', 'Unknown'),
|
||||||
|
"version": pkg.get('latest_absolute_version', 'N/A'),
|
||||||
|
"author": pkg.get('author', 'N/A'),
|
||||||
|
"fhir_version": pkg.get('fhir_version', 'N/A'),
|
||||||
|
"version_count": pkg.get('version_count', 0),
|
||||||
|
"canonical": pkg.get('canonical', 'N/A')
|
||||||
|
})
|
||||||
|
break
|
||||||
|
|
||||||
|
return render_template('package.dependents.html', dependents=dependents)
|
||||||
@ -5,3 +5,10 @@ requests==2.31.0
|
|||||||
Flask-WTF==1.2.1
|
Flask-WTF==1.2.1
|
||||||
WTForms==3.1.2
|
WTForms==3.1.2
|
||||||
Pytest
|
Pytest
|
||||||
|
pyyaml==6.0.1
|
||||||
|
fhir.resources==8.0.0
|
||||||
|
Flask-Migrate==4.1.0
|
||||||
|
cachetools
|
||||||
|
beautifulsoup4
|
||||||
|
feedparser==6.0.11
|
||||||
|
flasgger
|
||||||
|
|||||||
4358
services.py
4358
services.py
File diff suppressed because it is too large
Load Diff
395
setup_linux.sh
Normal file
395
setup_linux.sh
Normal file
@ -0,0 +1,395 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# --- Configuration ---
|
||||||
|
REPO_URL_HAPI="https://github.com/hapifhir/hapi-fhir-jpaserver-starter.git"
|
||||||
|
REPO_URL_CANDLE="https://github.com/FHIR/fhir-candle.git"
|
||||||
|
CLONE_DIR_HAPI="hapi-fhir-jpaserver"
|
||||||
|
CLONE_DIR_CANDLE="fhir-candle"
|
||||||
|
SOURCE_CONFIG_DIR="hapi-fhir-Setup"
|
||||||
|
CONFIG_FILE="application.yaml"
|
||||||
|
|
||||||
|
# --- Define Paths ---
|
||||||
|
SOURCE_CONFIG_PATH="../${SOURCE_CONFIG_DIR}/target/classes/${CONFIG_FILE}"
|
||||||
|
DEST_CONFIG_PATH="${CLONE_DIR_HAPI}/target/classes/${CONFIG_FILE}"
|
||||||
|
|
||||||
|
APP_MODE=""
|
||||||
|
CUSTOM_FHIR_URL_VAL=""
|
||||||
|
SERVER_TYPE=""
|
||||||
|
CANDLE_FHIR_VERSION=""
|
||||||
|
|
||||||
|
# --- Error Handling Function ---
|
||||||
|
handle_error() {
|
||||||
|
echo "------------------------------------"
|
||||||
|
echo "An error occurred: $1"
|
||||||
|
echo "Script aborted."
|
||||||
|
echo "------------------------------------"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# === MODIFIED: Prompt for Installation Mode ===
|
||||||
|
get_mode_choice() {
|
||||||
|
echo ""
|
||||||
|
echo "Select Installation Mode:"
|
||||||
|
echo "1. Lite (Excludes local HAPI FHIR Server - No Git/Maven/Dotnet needed)"
|
||||||
|
echo "2. Custom URL (Uses a custom FHIR Server - No Git/Maven/Dotnet needed)"
|
||||||
|
echo "3. Hapi (Includes local HAPI FHIR Server - Requires Git & Maven)"
|
||||||
|
echo "4. Candle (Includes local FHIR Candle Server - Requires Git & Dotnet)"
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
read -r -p "Enter your choice (1, 2, 3, or 4): " choice
|
||||||
|
case "$choice" in
|
||||||
|
1)
|
||||||
|
APP_MODE="lite"
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
APP_MODE="standalone"
|
||||||
|
get_custom_url_prompt
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
3)
|
||||||
|
APP_MODE="standalone"
|
||||||
|
SERVER_TYPE="hapi"
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
4)
|
||||||
|
APP_MODE="standalone"
|
||||||
|
SERVER_TYPE="candle"
|
||||||
|
get_candle_fhir_version
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Invalid input. Please try again."
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
echo "Selected Mode: $APP_MODE"
|
||||||
|
echo "Server Type: $SERVER_TYPE"
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
# === NEW: Prompt for Custom URL ===
|
||||||
|
get_custom_url_prompt() {
|
||||||
|
local confirmed_url=""
|
||||||
|
while true; do
|
||||||
|
echo
|
||||||
|
read -r -p "Please enter the custom FHIR server URL: " custom_url_input
|
||||||
|
echo
|
||||||
|
echo "You entered: $custom_url_input"
|
||||||
|
read -r -p "Is this URL correct? (Y/N): " confirm_url
|
||||||
|
if [[ "$confirm_url" =~ ^[Yy]$ ]]; then
|
||||||
|
confirmed_url="$custom_url_input"
|
||||||
|
break
|
||||||
|
else
|
||||||
|
echo "URL not confirmed. Please re-enter."
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
echo
|
||||||
|
read -r -p "Please re-enter the URL to confirm it is correct: " custom_url_input
|
||||||
|
if [ "$custom_url_input" = "$confirmed_url" ]; then
|
||||||
|
CUSTOM_FHIR_URL_VAL="$custom_url_input"
|
||||||
|
echo
|
||||||
|
echo "Custom URL confirmed: $CUSTOM_FHIR_URL_VAL"
|
||||||
|
break
|
||||||
|
else
|
||||||
|
echo
|
||||||
|
echo "URLs do not match. Please try again."
|
||||||
|
confirmed_url="$custom_url_input"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# === NEW: Prompt for Candle FHIR version ===
|
||||||
|
get_candle_fhir_version() {
|
||||||
|
echo ""
|
||||||
|
echo "Select the FHIR version for the Candle server:"
|
||||||
|
echo "1. R4 (4.0)"
|
||||||
|
echo "2. R4B (4.3)"
|
||||||
|
echo "3. R5 (5.0)"
|
||||||
|
while true; do
|
||||||
|
read -r -p "Enter your choice (1, 2, or 3): " choice
|
||||||
|
case "$choice" in
|
||||||
|
1)
|
||||||
|
CANDLE_FHIR_VERSION=r4
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
CANDLE_FHIR_VERSION=r4b
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
3)
|
||||||
|
CANDLE_FHIR_VERSION=r5
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Invalid input. Please try again."
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Call the function to get mode choice
|
||||||
|
get_mode_choice
|
||||||
|
|
||||||
|
# === Conditionally Execute Server Setup ===
|
||||||
|
case "$SERVER_TYPE" in
|
||||||
|
"hapi")
|
||||||
|
echo "Running Hapi server setup..."
|
||||||
|
echo
|
||||||
|
|
||||||
|
# --- Step 0: Clean up previous clone (optional) ---
|
||||||
|
echo "Checking for existing directory: $CLONE_DIR_HAPI"
|
||||||
|
if [ -d "$CLONE_DIR_HAPI" ]; then
|
||||||
|
echo "Found existing directory, removing it..."
|
||||||
|
rm -rf "$CLONE_DIR_HAPI"
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
handle_error "Failed to remove existing directory: $CLONE_DIR_HAPI"
|
||||||
|
fi
|
||||||
|
echo "Existing directory removed."
|
||||||
|
else
|
||||||
|
echo "Directory does not exist, proceeding with clone."
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
|
||||||
|
# --- Step 1: Clone the HAPI FHIR server repository ---
|
||||||
|
echo "Cloning repository: $REPO_URL_HAPI into $CLONE_DIR_HAPI..."
|
||||||
|
git clone "$REPO_URL_HAPI" "$CLONE_DIR_HAPI"
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
handle_error "Failed to clone repository. Check Git installation and network connection."
|
||||||
|
fi
|
||||||
|
echo "Repository cloned successfully."
|
||||||
|
echo
|
||||||
|
|
||||||
|
# --- Step 2: Navigate into the cloned directory ---
|
||||||
|
echo "Changing directory to $CLONE_DIR_HAPI..."
|
||||||
|
cd "$CLONE_DIR_HAPI" || handle_error "Failed to change directory to $CLONE_DIR_HAPI."
|
||||||
|
echo "Current directory: $(pwd)"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# --- Step 3: Build the HAPI server using Maven ---
|
||||||
|
echo "===> Starting Maven build (Step 3)..."
|
||||||
|
mvn clean package -DskipTests=true -Pboot
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "ERROR: Maven build failed."
|
||||||
|
cd ..
|
||||||
|
handle_error "Maven build process resulted in an error."
|
||||||
|
fi
|
||||||
|
echo "Maven build completed successfully."
|
||||||
|
echo
|
||||||
|
|
||||||
|
# --- Step 4: Copy the configuration file ---
|
||||||
|
echo "===> Starting file copy (Step 4)..."
|
||||||
|
echo "Copying configuration file..."
|
||||||
|
INITIAL_SCRIPT_DIR=$(pwd)
|
||||||
|
ABSOLUTE_SOURCE_CONFIG_PATH="${INITIAL_SCRIPT_DIR}/../${SOURCE_CONFIG_DIR}/target/classes/${CONFIG_FILE}"
|
||||||
|
|
||||||
|
echo "Source: $ABSOLUTE_SOURCE_CONFIG_PATH"
|
||||||
|
echo "Destination: target/classes/$CONFIG_FILE"
|
||||||
|
|
||||||
|
if [ ! -f "$ABSOLUTE_SOURCE_CONFIG_PATH" ]; then
|
||||||
|
echo "WARNING: Source configuration file not found at $ABSOLUTE_SOURCE_CONFIG_PATH."
|
||||||
|
echo "The script will continue, but the server might use default configuration."
|
||||||
|
else
|
||||||
|
cp "$ABSOLUTE_SOURCE_CONFIG_PATH" "target/classes/"
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "WARNING: Failed to copy configuration file. Check if the source file exists and permissions."
|
||||||
|
echo "The script will continue, but the server might use default configuration."
|
||||||
|
else
|
||||||
|
echo "Configuration file copied successfully."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
|
||||||
|
# --- Step 5: Navigate back to the parent directory ---
|
||||||
|
echo "===> Changing directory back (Step 5)..."
|
||||||
|
cd .. || handle_error "Failed to change back to the parent directory."
|
||||||
|
echo "Current directory: $(pwd)"
|
||||||
|
echo
|
||||||
|
;;
|
||||||
|
|
||||||
|
"candle")
|
||||||
|
echo "Running FHIR Candle server setup..."
|
||||||
|
echo
|
||||||
|
|
||||||
|
# --- Step 0: Clean up previous clone (optional) ---
|
||||||
|
echo "Checking for existing directory: $CLONE_DIR_CANDLE"
|
||||||
|
if [ -d "$CLONE_DIR_CANDLE" ]; then
|
||||||
|
echo "Found existing directory, removing it..."
|
||||||
|
rm -rf "$CLONE_DIR_CANDLE"
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
handle_error "Failed to remove existing directory: $CLONE_DIR_CANDLE"
|
||||||
|
fi
|
||||||
|
echo "Existing directory removed."
|
||||||
|
else
|
||||||
|
echo "Directory does not exist, proceeding with clone."
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
|
||||||
|
# --- Step 1: Clone the FHIR Candle server repository ---
|
||||||
|
echo "Cloning repository: $REPO_URL_CANDLE into $CLONE_DIR_CANDLE..."
|
||||||
|
git clone "$REPO_URL_CANDLE" "$CLONE_DIR_CANDLE"
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
handle_error "Failed to clone repository. Check Git and Dotnet SDK installation and network connection."
|
||||||
|
fi
|
||||||
|
echo "Repository cloned successfully."
|
||||||
|
echo
|
||||||
|
|
||||||
|
# --- Step 2: Navigate into the cloned directory ---
|
||||||
|
echo "Changing directory to $CLONE_DIR_CANDLE..."
|
||||||
|
cd "$CLONE_DIR_CANDLE" || handle_error "Failed to change directory to $CLONE_DIR_CANDLE."
|
||||||
|
echo "Current directory: $(pwd)"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# --- Step 3: Build the FHIR Candle server using Dotnet ---
|
||||||
|
echo "===> Starting Dotnet build (Step 3)..."
|
||||||
|
dotnet publish -c Release -f net9.0 -o publish
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
handle_error "Dotnet build failed. Check Dotnet SDK installation."
|
||||||
|
fi
|
||||||
|
echo "Dotnet build completed successfully."
|
||||||
|
echo
|
||||||
|
|
||||||
|
# --- Step 4: Navigate back to the parent directory ---
|
||||||
|
echo "===> Changing directory back (Step 4)..."
|
||||||
|
cd .. || handle_error "Failed to change back to the parent directory."
|
||||||
|
echo "Current directory: $(pwd)"
|
||||||
|
echo
|
||||||
|
;;
|
||||||
|
|
||||||
|
*) # APP_MODE is Lite, no SERVER_TYPE
|
||||||
|
echo "Running Lite setup, skipping server build..."
|
||||||
|
if [ -d "$CLONE_DIR_HAPI" ]; then
|
||||||
|
echo "Found existing HAPI directory in Lite mode. Removing it to avoid build issues..."
|
||||||
|
rm -rf "$CLONE_DIR_HAPI"
|
||||||
|
fi
|
||||||
|
if [ -d "$CLONE_DIR_CANDLE" ]; then
|
||||||
|
echo "Found existing Candle directory in Lite mode. Removing it to avoid build issues..."
|
||||||
|
rm -rf "$CLONE_DIR_CANDLE"
|
||||||
|
fi
|
||||||
|
mkdir -p "${CLONE_DIR_HAPI}/target/classes"
|
||||||
|
mkdir -p "${CLONE_DIR_HAPI}/custom"
|
||||||
|
touch "${CLONE_DIR_HAPI}/target/ROOT.war"
|
||||||
|
touch "${CLONE_DIR_HAPI}/target/classes/application.yaml"
|
||||||
|
mkdir -p "${CLONE_DIR_CANDLE}/publish"
|
||||||
|
touch "${CLONE_DIR_CANDLE}/publish/fhir-candle.dll"
|
||||||
|
echo "Placeholder files and directories created for Lite mode build."
|
||||||
|
echo
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# === MODIFIED: Update docker-compose.yml to set APP_MODE and HAPI_FHIR_URL and DOCKERFILE ===
|
||||||
|
echo "Updating docker-compose.yml with APP_MODE=$APP_MODE and HAPI_FHIR_URL..."
|
||||||
|
DOCKER_COMPOSE_TMP="docker-compose.yml.tmp"
|
||||||
|
DOCKER_COMPOSE_ORIG="docker-compose.yml"
|
||||||
|
|
||||||
|
HAPI_URL_TO_USE="https://fhir.hl7.org.au/aucore/fhir/DEFAULT/"
|
||||||
|
if [ -n "$CUSTOM_FHIR_URL_VAL" ]; then
|
||||||
|
HAPI_URL_TO_USE="$CUSTOM_FHIR_URL_VAL"
|
||||||
|
elif [ "$SERVER_TYPE" = "candle" ]; then
|
||||||
|
HAPI_URL_TO_USE="http://localhost:5826/fhir/${CANDLE_FHIR_VERSION}"
|
||||||
|
else
|
||||||
|
HAPI_URL_TO_USE="http://localhost:8080/fhir"
|
||||||
|
fi
|
||||||
|
|
||||||
|
DOCKERFILE_TO_USE="Dockerfile.lite"
|
||||||
|
if [ "$SERVER_TYPE" = "hapi" ]; then
|
||||||
|
DOCKERFILE_TO_USE="Dockerfile.hapi"
|
||||||
|
elif [ "$SERVER_TYPE" = "candle" ]; then
|
||||||
|
DOCKERFILE_TO_USE="Dockerfile.candle"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat << EOF > "$DOCKER_COMPOSE_TMP"
|
||||||
|
version: '3.8'
|
||||||
|
services:
|
||||||
|
fhirflare:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ${DOCKERFILE_TO_USE}
|
||||||
|
ports:
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ "$SERVER_TYPE" = "candle" ]; then
|
||||||
|
cat << EOF >> "$DOCKER_COMPOSE_TMP"
|
||||||
|
- "5000:5000"
|
||||||
|
- "5001:5826"
|
||||||
|
EOF
|
||||||
|
else
|
||||||
|
cat << EOF >> "$DOCKER_COMPOSE_TMP"
|
||||||
|
- "5000:5000"
|
||||||
|
- "8080:8080"
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat << EOF >> "$DOCKER_COMPOSE_TMP"
|
||||||
|
volumes:
|
||||||
|
- ./instance:/app/instance
|
||||||
|
- ./static/uploads:/app/static/uploads
|
||||||
|
- ./logs:/app/logs
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ "$SERVER_TYPE" = "hapi" ]; then
|
||||||
|
cat << EOF >> "$DOCKER_COMPOSE_TMP"
|
||||||
|
- ./instance/hapi-h2-data/:/app/h2-data # Keep volume mounts consistent
|
||||||
|
- ./hapi-fhir-jpaserver/target/ROOT.war:/usr/local/tomcat/webapps/ROOT.war
|
||||||
|
- ./hapi-fhir-jpaserver/target/classes/application.yaml:/usr/local/tomcat/conf/application.yaml
|
||||||
|
EOF
|
||||||
|
elif [ "$SERVER_TYPE" = "candle" ]; then
|
||||||
|
cat << EOF >> "$DOCKER_COMPOSE_TMP"
|
||||||
|
- ./fhir-candle/publish/:/app/fhir-candle-publish/
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat << EOF >> "$DOCKER_COMPOSE_TMP"
|
||||||
|
environment:
|
||||||
|
- FLASK_APP=app.py
|
||||||
|
- FLASK_ENV=development
|
||||||
|
- NODE_PATH=/usr/lib/node_modules
|
||||||
|
- APP_MODE=${APP_MODE}
|
||||||
|
- APP_BASE_URL=http://localhost:5000
|
||||||
|
- HAPI_FHIR_URL=${HAPI_URL_TO_USE}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ "$SERVER_TYPE" = "candle" ]; then
|
||||||
|
cat << EOF >> "$DOCKER_COMPOSE_TMP"
|
||||||
|
- ASPNETCORE_URLS=http://0.0.0.0:5826
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat << EOF >> "$DOCKER_COMPOSE_TMP"
|
||||||
|
command: supervisord -c /etc/supervisord.conf
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ ! -f "$DOCKER_COMPOSE_TMP" ]; then
|
||||||
|
handle_error "Failed to create temporary docker-compose file ($DOCKER_COMPOSE_TMP)."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Replace the original docker-compose.yml
|
||||||
|
mv "$DOCKER_COMPOSE_TMP" "$DOCKER_COMPOSE_ORIG"
|
||||||
|
echo "docker-compose.yml updated successfully."
|
||||||
|
echo
|
||||||
|
|
||||||
|
# --- Step 6: Build Docker images ---
|
||||||
|
echo "===> Starting Docker build (Step 6)..."
|
||||||
|
docker-compose build --no-cache
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
handle_error "Docker Compose build failed. Check Docker installation and docker-compose.yml file."
|
||||||
|
fi
|
||||||
|
echo "Docker images built successfully."
|
||||||
|
echo
|
||||||
|
|
||||||
|
# --- Step 7: Start Docker containers ---
|
||||||
|
echo "===> Starting Docker containers (Step 7)..."
|
||||||
|
docker-compose up -d
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
handle_error "Docker Compose up failed. Check Docker installation and container configurations."
|
||||||
|
fi
|
||||||
|
echo "Docker containers started successfully."
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "===================================="
|
||||||
|
echo "Script finished successfully! (Mode: $APP_MODE)"
|
||||||
|
echo "===================================="
|
||||||
|
exit 0
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 449 KiB After Width: | Height: | Size: 706 KiB |
1
static/animations/Validation abstract.json
Normal file
1
static/animations/Validation abstract.json
Normal file
File diff suppressed because one or more lines are too long
1
static/animations/import-fire.json
Normal file
1
static/animations/import-fire.json
Normal file
File diff suppressed because one or more lines are too long
390
static/css/animation.css
Normal file
390
static/css/animation.css
Normal file
@ -0,0 +1,390 @@
|
|||||||
|
body {
|
||||||
|
width: 100%;
|
||||||
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Apply overflow and height constraints only for fire animation page
|
||||||
|
body.fire-animation-page {
|
||||||
|
overflow: hidden;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
/* Fire animation overlay */
|
||||||
|
.fire-on {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(#1d4456, #112630);
|
||||||
|
opacity: 1;
|
||||||
|
z-index: 1;
|
||||||
|
transition: all 1200ms linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-center {
|
||||||
|
position: relative;
|
||||||
|
width: 300px; /* Reduced size for landing page */
|
||||||
|
height: 300px;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 8px solid rgba(0,0,0,.2);
|
||||||
|
border-radius: 50%;
|
||||||
|
z-index: 5;
|
||||||
|
background-color: #1d4456;
|
||||||
|
box-shadow: 0 0 50px 5px rgba(255,148,0,.1);
|
||||||
|
transition: all 500ms linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wood and star using local images */
|
||||||
|
.wood {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 21;
|
||||||
|
left: 50%;
|
||||||
|
bottom: 12%;
|
||||||
|
width: 80px;
|
||||||
|
margin-left: -40px;
|
||||||
|
height: 30px;
|
||||||
|
background-image: url('{{ url_for('static', filename='img/wood.png') }}');
|
||||||
|
background-size: 80px 30px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.star {
|
||||||
|
z-index: 2;
|
||||||
|
position: absolute;
|
||||||
|
top: 138px;
|
||||||
|
left: 18px;
|
||||||
|
background-image: url('{{ url_for('static', filename='img/star.png') }}');
|
||||||
|
background-size: 11px 11px;
|
||||||
|
width: 11px;
|
||||||
|
height: 11px;
|
||||||
|
opacity: 0.4;
|
||||||
|
animation: starShine 3.5s linear infinite;
|
||||||
|
transition: all 1200ms linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wood-circle {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 20;
|
||||||
|
left: 50%;
|
||||||
|
bottom: 11%;
|
||||||
|
width: 100px;
|
||||||
|
margin-left: -50px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 100%;
|
||||||
|
background-color: #0a171d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 6;
|
||||||
|
right: -225px;
|
||||||
|
bottom: -337px;
|
||||||
|
width: 562px;
|
||||||
|
height: 525px;
|
||||||
|
border-radius: 100%;
|
||||||
|
background-color: #112630;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Moon */
|
||||||
|
.moon {
|
||||||
|
position: absolute;
|
||||||
|
top: 37px;
|
||||||
|
left: 86px;
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
background-color: #b2b7bc;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: inset -15px 1.5px 0 0px #c0c3c9, 0 0 7px 3px rgba(228,228,222,.4);
|
||||||
|
z-index: 1;
|
||||||
|
animation: brilla-moon 4s alternate infinite;
|
||||||
|
transition: all 2000ms linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.moon div:nth-child(1) {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 10%;
|
||||||
|
width: 12%;
|
||||||
|
height: 12%;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid #adaca2;
|
||||||
|
box-shadow: inset 1.5px -0.75px 0 0px #85868b;
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.moon div:nth-child(2) {
|
||||||
|
position: absolute;
|
||||||
|
top: 20%;
|
||||||
|
left: 38%;
|
||||||
|
width: 16%;
|
||||||
|
height: 16%;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid #adaca2;
|
||||||
|
box-shadow: inset 1.5px -0.75px 0 0px #85868b;
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.moon div:nth-child(3) {
|
||||||
|
position: absolute;
|
||||||
|
top: 60%;
|
||||||
|
left: 45%;
|
||||||
|
width: 20%;
|
||||||
|
height: 20%;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid #adaca2;
|
||||||
|
box-shadow: inset 1.5px -0.75px 0 0px #85868b;
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes brilla-moon {
|
||||||
|
0% { box-shadow: inset -15px 1.5px 0 0px #c0c3c9, 0 0 7px 3px rgba(228,228,222,.4); }
|
||||||
|
50% { box-shadow: inset -15px 1.5px 0 0px #c0c3c9, 0 0 11px 6px rgba(228,228,222,.4); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Shooting stars */
|
||||||
|
.shooting-star {
|
||||||
|
z-index: 2;
|
||||||
|
width: 1px;
|
||||||
|
height: 37px;
|
||||||
|
border-bottom-left-radius: 50%;
|
||||||
|
border-bottom-right-radius: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -52px;
|
||||||
|
background: linear-gradient(to bottom, rgba(255, 255, 255, 0), white);
|
||||||
|
animation: animShootingStar 6s linear infinite;
|
||||||
|
transition: all 2000ms linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animShootingStar {
|
||||||
|
from { transform: translateY(0px) translateX(0px) rotate(-45deg); opacity: 1; height: 3px; }
|
||||||
|
to { transform: translateY(960px) translateX(960px) rotate(-45deg); opacity: 1; height: 600px; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.shooting-star-2 {
|
||||||
|
z-index: 2;
|
||||||
|
width: 1px;
|
||||||
|
height: 37px;
|
||||||
|
border-bottom-left-radius: 50%;
|
||||||
|
border-bottom-right-radius: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 150px;
|
||||||
|
background: linear-gradient(to bottom, rgba(255, 255, 255, 0), white);
|
||||||
|
animation: animShootingStar-2 9s linear infinite;
|
||||||
|
transition: all 2000ms linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animShootingStar-2 {
|
||||||
|
from { transform: translateY(0px) translateX(0px) rotate(-45deg); opacity: 1; height: 3px; }
|
||||||
|
to { transform: translateY(1440px) translateX(1440px) rotate(-45deg); opacity: 1; height: 600px; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stars */
|
||||||
|
.star.snd { top: 75px; left: 232px; animation-delay: 1s; }
|
||||||
|
.star.trd { top: 97px; left: 75px; animation-delay: 1.4s; }
|
||||||
|
.star.fth { top: 15px; left: 150px; animation-delay: 1.8s; }
|
||||||
|
.star.fith { top: 63px; left: 165px; animation-delay: 2.2s; }
|
||||||
|
|
||||||
|
@keyframes starShine {
|
||||||
|
0% { transform: scale(0.3) rotate(0deg); opacity: 0.4; }
|
||||||
|
25% { transform: scale(1) rotate(360deg); opacity: 1; }
|
||||||
|
50% { transform: scale(0.3) rotate(720deg); opacity: 0.4; }
|
||||||
|
100% { transform: scale(0.3) rotate(0deg); opacity: 0.4; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Trees */
|
||||||
|
.tree-1 {
|
||||||
|
position: relative;
|
||||||
|
top: 112px;
|
||||||
|
left: 37px;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
z-index: 8;
|
||||||
|
border-bottom: 67px solid #0a171d;
|
||||||
|
border-left: 22px solid transparent;
|
||||||
|
border-right: 22px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-1:before {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -82px;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -3px;
|
||||||
|
width: 6px;
|
||||||
|
height: 22px;
|
||||||
|
z-index: 7;
|
||||||
|
content: '';
|
||||||
|
background-color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-2 {
|
||||||
|
position: relative;
|
||||||
|
top: 0;
|
||||||
|
left: 187px;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
z-index: 8;
|
||||||
|
border-bottom: 67px solid #0a171d;
|
||||||
|
border-left: 22px solid transparent;
|
||||||
|
border-right: 22px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-2:before {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -82px;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -3px;
|
||||||
|
width: 6px;
|
||||||
|
height: 22px;
|
||||||
|
z-index: 7;
|
||||||
|
content: '';
|
||||||
|
background-color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fire */
|
||||||
|
.fire {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 39;
|
||||||
|
width: 2px;
|
||||||
|
margin-left: -1px;
|
||||||
|
left: 50%;
|
||||||
|
bottom: 60px;
|
||||||
|
transition: all 1200ms linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fire span {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
bottom: -11px;
|
||||||
|
margin-left: -15px;
|
||||||
|
height: 0;
|
||||||
|
width: 0;
|
||||||
|
border: 22px solid #febd08; /* Main flame: yellow-orange */
|
||||||
|
border-radius: 50%;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
left: -6px;
|
||||||
|
box-shadow: 0 0 7px 3px rgba(244,110,28,0.8), 0 0 15px 7px rgba(244,110,28,0.6), 0 0 22px 11px rgba(244,110,28,0.3);
|
||||||
|
transform: scale(0.45, 0.75) rotate(45deg);
|
||||||
|
animation: brilla-fire 2.5s alternate infinite;
|
||||||
|
z-index: 9;
|
||||||
|
transition: all 1200ms linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fire span:nth-child(2) {
|
||||||
|
left: -16px;
|
||||||
|
border: 22px solid #e63946; /* Outside flame: red */
|
||||||
|
box-shadow: 0 0 7px 3px rgba(230,57,70,0.8), 0 0 15px 7px rgba(230,57,70,0.6), 0 0 22px 11px rgba(230,57,70,0.3);
|
||||||
|
transform: scale(0.3, 0.55) rotate(15deg);
|
||||||
|
z-index: 8;
|
||||||
|
animation: brilla-fire-red 1.5s alternate infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fire span:nth-child(3) {
|
||||||
|
left: 3px;
|
||||||
|
border: 22px solid #e63946; /* Outside flame: red */
|
||||||
|
box-shadow: 0 0 7px 3px rgba(230,57,70,0.8), 0 0 15px 7px rgba(230,57,70,0.6), 0 0 22px 11px rgba(230,57,70,0.3);
|
||||||
|
transform: scale(0.3, 0.55) rotate(80deg);
|
||||||
|
z-index: 8;
|
||||||
|
animation: brilla-fire-red 2s alternate infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fire span:after {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
bottom: -22px;
|
||||||
|
content: '';
|
||||||
|
margin-left: -3px;
|
||||||
|
height: 22px;
|
||||||
|
width: 9px;
|
||||||
|
background-color: rgba(244,110,28,0.7);
|
||||||
|
border-radius: 80px;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
box-shadow: 0 0 15px 7px rgba(244,110,28,0.7);
|
||||||
|
left: -6px;
|
||||||
|
opacity: 0.8;
|
||||||
|
transform: rotate(-50deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fire span:nth-child(2):after {
|
||||||
|
background-color: rgba(230,57,70,0.7); /* Match red flame */
|
||||||
|
box-shadow: 0 0 15px 7px rgba(230,57,70,0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fire span:nth-child(3):after {
|
||||||
|
background-color: rgba(230,57,70,0.7); /* Match red flame */
|
||||||
|
box-shadow: 0 0 15px 7px rgba(230,57,70,0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes brilla-fire {
|
||||||
|
0%, 100% { box-shadow: 0 0 7px 3px rgba(244,110,28,0.8), 0 0 15px 7px rgba(244,110,28,0.6), 0 0 22px 11px rgba(244,110,28,0.3); }
|
||||||
|
50% { box-shadow: 0 0 10px 5px rgba(244,110,28,0.8), 0 0 21px 10px rgba(244,110,28,0.6), 0 0 31px 15px rgba(244,110,28,0.3); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes brilla-fire-red {
|
||||||
|
0%, 100% { box-shadow: 0 0 7px 3px rgba(230,57,70,0.8), 0 0 15px 7px rgba(230,57,70,0.6), 0 0 22px 11px rgba(230,57,70,0.3); }
|
||||||
|
50% { box-shadow: 0 0 10px 5px rgba(230,57,70,0.8), 0 0 21px 10px rgba(230,57,70,0.6), 0 0 31px 15px rgba(230,57,70,0.3); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Smoke */
|
||||||
|
.smoke {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 40;
|
||||||
|
width: 2px;
|
||||||
|
margin-left: -1px;
|
||||||
|
left: 50%;
|
||||||
|
bottom: 79px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: all 800ms linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.smoke span {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
bottom: -26px;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -15px;
|
||||||
|
height: 0;
|
||||||
|
width: 0;
|
||||||
|
border: 22px solid rgba(0, 0, 0, .6);
|
||||||
|
border-radius: 16px;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
left: -6px;
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.2, 0.2) rotate(-45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes smokeLeft {
|
||||||
|
0% { transform: scale(0.2, 0.2) translate(0, 0) rotate(-45deg); }
|
||||||
|
10% { opacity: 1; transform: scale(0.2, 0.3) translate(0, -3px) rotate(-45deg); }
|
||||||
|
60% { opacity: 0.6; transform: scale(0.3, 0.5) translate(-7px, -60px) rotate(-45deg); }
|
||||||
|
100% { opacity: 0; transform: scale(0.4, 0.8) translate(-15px, -90px) rotate(-45deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes smokeRight {
|
||||||
|
0% { transform: scale(0.2, 0.2) translate(0, 0) rotate(-45deg); }
|
||||||
|
10% { opacity: 1; transform: scale(0.2, 0.3) translate(0, -3px) rotate(-45deg); }
|
||||||
|
60% { opacity: 0.6; transform: scale(0.3, 0.5) translate(7px, -60px) rotate(-45deg); }
|
||||||
|
100% { opacity: 0; transform: scale(0.4, 0.8) translate(15px, -90px) rotate(-45deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.smoke .s-0 { animation: smokeLeft 7s 0s infinite; }
|
||||||
|
.smoke .s-1 { animation: smokeRight 7s 0.7s infinite; }
|
||||||
|
.smoke .s-2 { animation: smokeLeft 7s 1.4s infinite; }
|
||||||
|
.smoke .s-3 { animation: smokeRight 7s 2.1s infinite; }
|
||||||
|
.smoke .s-4 { animation: smokeLeft 7s 2.8s infinite; }
|
||||||
|
.smoke .s-5 { animation: smokeRight 7s 3.5s infinite; }
|
||||||
|
.smoke .s-6 { animation: smokeLeft 7s 4.2s infinite; }
|
||||||
|
.smoke .s-7 { animation: smokeRight 7s 4.9s infinite; }
|
||||||
|
.smoke .s-8 { animation: smokeLeft 7s 5.6s infinite; }
|
||||||
|
.smoke .s-9 { animation: smokeRight 7s 6.3s infinite; }
|
||||||
|
|
||||||
|
/* Fire-off state (light theme) */
|
||||||
|
body:not(.fire-on) .section-center { box-shadow: 0 0 50px 5px rgba(200,200,200,.2); }
|
||||||
|
body:not(.fire-on) .smoke { opacity: 1; transition-delay: 0.8s; }
|
||||||
|
body:not(.fire-on) .fire span { bottom: -26px; transform: scale(0.15, 0.15) rotate(45deg); }
|
||||||
223
static/css/fire-animation.css
Normal file
223
static/css/fire-animation.css
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
/* /app/static/css/fire-animation.css */
|
||||||
|
|
||||||
|
/* Removed html, body, stage styles */
|
||||||
|
|
||||||
|
.minifire-container { /* Add a wrapper for positioning/sizing */
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
height: 130px; /* Overall height of the animation area */
|
||||||
|
overflow: hidden; /* Hide parts extending beyond the container */
|
||||||
|
background-color: #270537; /* Optional: Add a background */
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #444;
|
||||||
|
margin-top: 0.5rem; /* Space above animation */
|
||||||
|
}
|
||||||
|
|
||||||
|
.minifire-campfire {
|
||||||
|
position: relative;
|
||||||
|
/* Base size significantly reduced (original was 600px) */
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
transform-origin: bottom center;
|
||||||
|
/* Scale down slightly more if needed, adjusted positioning based on origin */
|
||||||
|
transform: scale(0.8) translateY(15px); /* Pushes it down slightly */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Scaled Down Logs --- */
|
||||||
|
.minifire-log {
|
||||||
|
position: absolute;
|
||||||
|
width: 60px; /* 238/4 */
|
||||||
|
height: 18px; /* 70/4 */
|
||||||
|
border-radius: 8px; /* 32/4 */
|
||||||
|
background: #781e20;
|
||||||
|
overflow: hidden;
|
||||||
|
opacity: 0.99;
|
||||||
|
transform-origin: center center;
|
||||||
|
box-shadow: 0 0 1px 0.5px rgba(0,0,0,0.15); /* Scaled shadow */
|
||||||
|
}
|
||||||
|
|
||||||
|
.minifire-log:before {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 9px; /* 35/4 */
|
||||||
|
width: 2px; /* 8/4 */
|
||||||
|
height: 2px; /* 8/4 */
|
||||||
|
border-radius: 8px; /* 32/4 */
|
||||||
|
background: #b35050;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 3;
|
||||||
|
/* Scaled box-shadows */
|
||||||
|
box-shadow: 0 0 0 0.5px #781e20, /* 2.5/4 -> 0.6 -> 0.5 */
|
||||||
|
0 0 0 2.5px #b35050, /* 10.5/4 -> 2.6 -> 2.5 */
|
||||||
|
0 0 0 3.5px #781e20, /* 13/4 -> 3.25 -> 3.5 */
|
||||||
|
0 0 0 5.5px #b35050, /* 21/4 -> 5.25 -> 5.5 */
|
||||||
|
0 0 0 6px #781e20, /* 23.5/4 -> 5.9 -> 6 */
|
||||||
|
0 0 0 8px #b35050; /* 31.5/4 -> 7.9 -> 8 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.minifire-streak {
|
||||||
|
position: absolute;
|
||||||
|
height: 1px; /* Min height */
|
||||||
|
border-radius: 5px; /* 20/4 */
|
||||||
|
background: #b35050;
|
||||||
|
}
|
||||||
|
/* Scaled streaks */
|
||||||
|
.minifire-streak:nth-child(1) { top: 3px; width: 23px; } /* 10/4, 90/4 */
|
||||||
|
.minifire-streak:nth-child(2) { top: 3px; left: 25px; width: 20px; } /* 10/4, 100/4, 80/4 */
|
||||||
|
.minifire-streak:nth-child(3) { top: 3px; left: 48px; width: 8px; } /* 10/4, 190/4, 30/4 */
|
||||||
|
.minifire-streak:nth-child(4) { top: 6px; width: 33px; } /* 22/4, 132/4 */
|
||||||
|
.minifire-streak:nth-child(5) { top: 6px; left: 36px; width: 12px; } /* 22/4, 142/4, 48/4 */
|
||||||
|
.minifire-streak:nth-child(6) { top: 6px; left: 50px; width: 7px; } /* 22/4, 200/4, 28/4 */
|
||||||
|
.minifire-streak:nth-child(7) { top: 9px; left: 19px; width: 40px; } /* 34/4, 74/4, 160/4 */
|
||||||
|
.minifire-streak:nth-child(8) { top: 12px; left: 28px; width: 10px; } /* 46/4, 110/4, 40/4 */
|
||||||
|
.minifire-streak:nth-child(9) { top: 12px; left: 43px; width: 14px; } /* 46/4, 170/4, 54/4 */
|
||||||
|
.minifire-streak:nth-child(10) { top: 15px; left: 23px; width: 28px; } /* 58/4, 90/4, 110/4 */
|
||||||
|
|
||||||
|
/* Scaled Log Positions (Relative to 150px campfire) */
|
||||||
|
.minifire-log:nth-child(1) { bottom: 25px; left: 25px; transform: rotate(150deg) scaleX(0.75); z-index: 20; } /* 100/4, 100/4 */
|
||||||
|
.minifire-log:nth-child(2) { bottom: 30px; left: 35px; transform: rotate(110deg) scaleX(0.75); z-index: 10; } /* 120/4, 140/4 */
|
||||||
|
.minifire-log:nth-child(3) { bottom: 25px; left: 17px; transform: rotate(-10deg) scaleX(0.75); } /* 98/4, 68/4 */
|
||||||
|
.minifire-log:nth-child(4) { bottom: 20px; left: 55px; transform: rotate(-120deg) scaleX(0.75); z-index: 26; } /* 80/4, 220/4 */
|
||||||
|
.minifire-log:nth-child(5) { bottom: 19px; left: 53px; transform: rotate(-30deg) scaleX(0.75); z-index: 25; } /* 75/4, 210/4 */
|
||||||
|
.minifire-log:nth-child(6) { bottom: 23px; left: 70px; transform: rotate(35deg) scaleX(0.85); z-index: 30; } /* 92/4, 280/4 */
|
||||||
|
.minifire-log:nth-child(7) { bottom: 18px; left: 75px; transform: rotate(-30deg) scaleX(0.75); z-index: 20; } /* 70/4, 300/4 */
|
||||||
|
|
||||||
|
/* --- Scaled Down Sticks --- */
|
||||||
|
.minifire-stick {
|
||||||
|
position: absolute;
|
||||||
|
width: 17px; /* 68/4 */
|
||||||
|
height: 5px; /* 20/4 */
|
||||||
|
border-radius: 3px; /* 10/4 */
|
||||||
|
box-shadow: 0 0 1px 0.5px rgba(0,0,0,0.1);
|
||||||
|
background: #781e20;
|
||||||
|
transform-origin: center center;
|
||||||
|
}
|
||||||
|
.minifire-stick:before {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 100%;
|
||||||
|
left: 7px; /* 30/4 -> 7.5 */
|
||||||
|
width: 1.5px; /* 6/4 */
|
||||||
|
height: 5px; /* 20/4 */
|
||||||
|
background: #781e20;
|
||||||
|
border-radius: 3px; /* 10/4 */
|
||||||
|
transform: translateY(50%) rotate(32deg);
|
||||||
|
}
|
||||||
|
.minifire-stick:after {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 5px; /* 20/4 */
|
||||||
|
height: 5px; /* 20/4 */
|
||||||
|
background: #b35050;
|
||||||
|
border-radius: 3px; /* 10/4 */
|
||||||
|
}
|
||||||
|
/* Scaled Stick Positions */
|
||||||
|
.minifire-stick:nth-child(1) { left: 40px; bottom: 41px; transform: rotate(-152deg) scaleX(0.8); z-index: 12; } /* 158/4, 164/4 */
|
||||||
|
.minifire-stick:nth-child(2) { left: 45px; bottom: 8px; transform: rotate(20deg) scaleX(0.9); } /* 180/4, 30/4 */
|
||||||
|
.minifire-stick:nth-child(3) { left: 100px; bottom: 10px; transform: rotate(170deg) scaleX(0.9); } /* 400/4, 38/4 */
|
||||||
|
.minifire-stick:nth-child(3):before { display: none; }
|
||||||
|
.minifire-stick:nth-child(4) { left: 93px; bottom: 38px; transform: rotate(80deg) scaleX(0.9); z-index: 20; } /* 370/4, 150/4 */
|
||||||
|
.minifire-stick:nth-child(4):before { display: none; }
|
||||||
|
|
||||||
|
/* --- Scaled Down Fire --- */
|
||||||
|
.minifire-fire .minifire-flame {
|
||||||
|
position: absolute;
|
||||||
|
transform-origin: bottom center;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Red Flames */
|
||||||
|
.minifire-fire__red .minifire-flame {
|
||||||
|
width: 12px; /* 48/4 */
|
||||||
|
border-radius: 12px; /* 48/4 */
|
||||||
|
background: #e20f00;
|
||||||
|
box-shadow: 0 0 20px 5px rgba(226,15,0,0.4); /* Scaled shadow */
|
||||||
|
}
|
||||||
|
/* Scaled positions/heights */
|
||||||
|
.minifire-fire__red .minifire-flame:nth-child(1) { left: 35px; height: 40px; bottom: 25px; animation: minifire-fire 2s 0.15s ease-in-out infinite alternate; } /* 138/4, 160/4, 100/4 */
|
||||||
|
.minifire-fire__red .minifire-flame:nth-child(2) { left: 47px; height: 60px; bottom: 25px; animation: minifire-fire 2s 0.35s ease-in-out infinite alternate; } /* 186/4, 240/4, 100/4 */
|
||||||
|
.minifire-fire__red .minifire-flame:nth-child(3) { left: 59px; height: 75px; bottom: 25px; animation: minifire-fire 2s 0.1s ease-in-out infinite alternate; } /* 234/4, 300/4, 100/4 */
|
||||||
|
.minifire-fire__red .minifire-flame:nth-child(4) { left: 71px; height: 90px; bottom: 25px; animation: minifire-fire 2s 0s ease-in-out infinite alternate; } /* 282/4, 360/4, 100/4 */
|
||||||
|
.minifire-fire__red .minifire-flame:nth-child(5) { left: 83px; height: 78px; bottom: 25px; animation: minifire-fire 2s 0.45s ease-in-out infinite alternate; } /* 330/4, 310/4, 100/4 */
|
||||||
|
.minifire-fire__red .minifire-flame:nth-child(6) { left: 95px; height: 58px; bottom: 25px; animation: minifire-fire 2s 0.3s ease-in-out infinite alternate; } /* 378/4, 232/4, 100/4 */
|
||||||
|
.minifire-fire__red .minifire-flame:nth-child(7) { left: 107px; height: 35px; bottom: 25px; animation: minifire-fire 2s 0.1s ease-in-out infinite alternate; } /* 426/4, 140/4, 100/4 */
|
||||||
|
|
||||||
|
/* Orange Flames */
|
||||||
|
.minifire-fire__orange .minifire-flame {
|
||||||
|
width: 12px; border-radius: 12px; background: #ff9c00;
|
||||||
|
box-shadow: 0 0 20px 5px rgba(255,156,0,0.4);
|
||||||
|
}
|
||||||
|
.minifire-fire__orange .minifire-flame:nth-child(1) { left: 35px; height: 35px; bottom: 25px; animation: minifire-fire 2s 0.05s ease-in-out infinite alternate; }
|
||||||
|
.minifire-fire__orange .minifire-flame:nth-child(2) { left: 47px; height: 53px; bottom: 25px; animation: minifire-fire 2s 0.1s ease-in-out infinite alternate; }
|
||||||
|
.minifire-fire__orange .minifire-flame:nth-child(3) { left: 59px; height: 63px; bottom: 25px; animation: minifire-fire 2s 0.35s ease-in-out infinite alternate; }
|
||||||
|
.minifire-fire__orange .minifire-flame:nth-child(4) { left: 71px; height: 75px; bottom: 25px; animation: minifire-fire 2s 0.4s ease-in-out infinite alternate; }
|
||||||
|
.minifire-fire__orange .minifire-flame:nth-child(5) { left: 83px; height: 65px; bottom: 25px; animation: minifire-fire 2s 0.5s ease-in-out infinite alternate; }
|
||||||
|
.minifire-fire__orange .minifire-flame:nth-child(6) { left: 95px; height: 51px; bottom: 25px; animation: minifire-fire 2s 0.35s ease-in-out infinite alternate; }
|
||||||
|
.minifire-fire__orange .minifire-flame:nth-child(7) { left: 107px; height: 28px; bottom: 25px; animation: minifire-fire 2s 0.1s ease-in-out infinite alternate; }
|
||||||
|
|
||||||
|
/* Yellow Flames */
|
||||||
|
.minifire-fire__yellow .minifire-flame {
|
||||||
|
width: 12px; border-radius: 12px; background: #ffeb6e;
|
||||||
|
box-shadow: 0 0 20px 5px rgba(255,235,110,0.4);
|
||||||
|
}
|
||||||
|
.minifire-fire__yellow .minifire-flame:nth-child(1) { left: 47px; height: 35px; bottom: 25px; animation: minifire-fire 2s 0.6s ease-in-out infinite alternate; }
|
||||||
|
.minifire-fire__yellow .minifire-flame:nth-child(2) { left: 59px; height: 43px; bottom: 30px; animation: minifire-fire 2s 0.4s ease-in-out infinite alternate; } /* Adjusted bottom slightly */
|
||||||
|
.minifire-fire__yellow .minifire-flame:nth-child(3) { left: 71px; height: 60px; bottom: 25px; animation: minifire-fire 2s 0.38s ease-in-out infinite alternate; }
|
||||||
|
.minifire-fire__yellow .minifire-flame:nth-child(4) { left: 83px; height: 50px; bottom: 25px; animation: minifire-fire 2s 0.22s ease-in-out infinite alternate; }
|
||||||
|
.minifire-fire__yellow .minifire-flame:nth-child(5) { left: 95px; height: 36px; bottom: 25px; animation: minifire-fire 2s 0.18s ease-in-out infinite alternate; }
|
||||||
|
|
||||||
|
/* White Flames */
|
||||||
|
.minifire-fire__white .minifire-flame {
|
||||||
|
width: 12px; border-radius: 12px; background: #fef1d9;
|
||||||
|
box-shadow: 0 0 20px 5px rgba(254,241,217,0.4);
|
||||||
|
}
|
||||||
|
.minifire-fire__white .minifire-flame:nth-child(1) { left: 39px; width: 8px; height: 25px; bottom: 25px; animation: minifire-fire 2s 0.22s ease-in-out infinite alternate; } /* Scaled width too */
|
||||||
|
.minifire-fire__white .minifire-flame:nth-child(2) { left: 45px; width: 8px; height: 30px; bottom: 25px; animation: minifire-fire 2s 0.42s ease-in-out infinite alternate; }
|
||||||
|
.minifire-fire__white .minifire-flame:nth-child(3) { left: 59px; height: 43px; bottom: 25px; animation: minifire-fire 2s 0.32s ease-in-out infinite alternate; }
|
||||||
|
.minifire-fire__white .minifire-flame:nth-child(4) { left: 71px; height: 53px; bottom: 25px; animation: minifire-fire 2s 0.8s ease-in-out infinite alternate; }
|
||||||
|
.minifire-fire__white .minifire-flame:nth-child(5) { left: 83px; height: 43px; bottom: 25px; animation: minifire-fire 2s 0.85s ease-in-out infinite alternate; }
|
||||||
|
.minifire-fire__white .minifire-flame:nth-child(6) { left: 95px; width: 8px; height: 28px; bottom: 25px; animation: minifire-fire 2s 0.64s ease-in-out infinite alternate; }
|
||||||
|
.minifire-fire__white .minifire-flame:nth-child(7) { left: 102px; width: 8px; height: 25px; bottom: 25px; animation: minifire-fire 2s 0.32s ease-in-out infinite alternate; }
|
||||||
|
|
||||||
|
/* --- Scaled Down Sparks --- */
|
||||||
|
.minifire-spark {
|
||||||
|
position: absolute;
|
||||||
|
width: 1.5px; /* 6/4 */
|
||||||
|
height: 5px; /* 20/4 */
|
||||||
|
background: #fef1d9;
|
||||||
|
border-radius: 5px; /* 18/4 -> 4.5 */
|
||||||
|
z-index: 50;
|
||||||
|
transform-origin: bottom center;
|
||||||
|
transform: scaleY(0);
|
||||||
|
}
|
||||||
|
/* Scaled spark positions/animations */
|
||||||
|
.minifire-spark:nth-child(1) { left: 40px; bottom: 53px; animation: minifire-spark 1s 0.4s linear infinite; } /* 160/4, 212/4 */
|
||||||
|
.minifire-spark:nth-child(2) { left: 45px; bottom: 60px; animation: minifire-spark 1s 1s linear infinite; } /* 180/4, 240/4 */
|
||||||
|
.minifire-spark:nth-child(3) { left: 52px; bottom: 80px; animation: minifire-spark 1s 0.8s linear infinite; } /* 208/4, 320/4 */
|
||||||
|
.minifire-spark:nth-child(4) { left: 78px; bottom: 100px; animation: minifire-spark 1s 2s linear infinite; } /* 310/4, 400/4 */
|
||||||
|
.minifire-spark:nth-child(5) { left: 90px; bottom: 95px; animation: minifire-spark 1s 0.75s linear infinite; } /* 360/4, 380/4 */
|
||||||
|
.minifire-spark:nth-child(6) { left: 98px; bottom: 80px; animation: minifire-spark 1s 0.65s linear infinite; } /* 390/4, 320/4 */
|
||||||
|
.minifire-spark:nth-child(7) { left: 100px; bottom: 70px; animation: minifire-spark 1s 1s linear infinite; } /* 400/4, 280/4 */
|
||||||
|
.minifire-spark:nth-child(8) { left: 108px; bottom: 53px; animation: minifire-spark 1s 1.4s linear infinite; } /* 430/4, 210/4 */
|
||||||
|
|
||||||
|
/* --- Keyframes (Rename to avoid conflicts) --- */
|
||||||
|
/* Use the same keyframe logic, just rename them */
|
||||||
|
@keyframes minifire-fire {
|
||||||
|
0% { transform: scaleY(1); } 28% { transform: scaleY(0.7); } 38% { transform: scaleY(0.8); } 50% { transform: scaleY(0.6); } 70% { transform: scaleY(0.95); } 82% { transform: scaleY(0.58); } 100% { transform: scaleY(1); }
|
||||||
|
}
|
||||||
|
@keyframes minifire-spark {
|
||||||
|
0%, 35% { transform: scaleY(0) translateY(0); opacity: 0; }
|
||||||
|
50% { transform: scaleY(1) translateY(0); opacity: 1; }
|
||||||
|
/* Adjusted translateY for smaller scale */
|
||||||
|
70% { transform: scaleY(1) translateY(-3px); opacity: 1; } /* 10/4 -> 2.5 -> 3 */
|
||||||
|
75% { transform: scaleY(1) translateY(-3px); opacity: 0; }
|
||||||
|
100% { transform: scaleY(0) translateY(0); opacity: 0; }
|
||||||
|
}
|
||||||
BIN
static/img/star.png
Normal file
BIN
static/img/star.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 436 B |
BIN
static/img/wood.png
Normal file
BIN
static/img/wood.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
1
static/js/htmx.min.js
vendored
Normal file
1
static/js/htmx.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -33,4 +33,18 @@ stdout_logfile_maxbytes=10MB
|
|||||||
stdout_logfile_backups=5
|
stdout_logfile_backups=5
|
||||||
stderr_logfile=/app/logs/tomcat_err.log
|
stderr_logfile=/app/logs/tomcat_err.log
|
||||||
stderr_logfile_maxbytes=10MB
|
stderr_logfile_maxbytes=10MB
|
||||||
stderr_logfile_backups=5
|
stderr_logfile_backups=5
|
||||||
|
|
||||||
|
[program:candle]
|
||||||
|
command=dotnet /app/fhir-candle-publish/fhir-candle.dll
|
||||||
|
directory=/app/fhir-candle-publish
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
startsecs=10
|
||||||
|
stopwaitsecs=10
|
||||||
|
stdout_logfile=/app/logs/candle.log
|
||||||
|
stdout_logfile_maxbytes=10MB
|
||||||
|
stdout_logfile_backups=5
|
||||||
|
stderr_logfile=/app/logs/candle_err.log
|
||||||
|
stderr_logfile_maxbytes=10MB
|
||||||
|
stderr_logfile_backups=5
|
||||||
|
|||||||
24
templates/_flash_messages.html
Normal file
24
templates/_flash_messages.html
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{# templates/_flash_messages.html #}
|
||||||
|
|
||||||
|
{# Check if there are any flashed messages #}
|
||||||
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
|
{% if messages %}
|
||||||
|
{# Loop through messages and display them as Bootstrap alerts #}
|
||||||
|
{% for category, message in messages %}
|
||||||
|
{# Map Flask message categories (e.g., 'error', 'success', 'warning') to Bootstrap alert classes #}
|
||||||
|
{% set alert_class = 'alert-info' %} {# Default class #}
|
||||||
|
{% if category == 'error' or category == 'danger' %}
|
||||||
|
{% set alert_class = 'alert-danger' %}
|
||||||
|
{% elif category == 'success' %}
|
||||||
|
{% set alert_class = 'alert-success' %}
|
||||||
|
{% elif category == 'warning' %}
|
||||||
|
{% set alert_class = 'alert-warning' %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="alert {{ alert_class }} alert-dismissible fade show" role="alert">
|
||||||
|
{{ message }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
113
templates/_search_results_table.html
Normal file
113
templates/_search_results_table.html
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
{# templates/_search_results_table.html #}
|
||||||
|
{# This partial template renders the search results table and pagination #}
|
||||||
|
|
||||||
|
{% if packages %}
|
||||||
|
<table class="table table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Package</th>
|
||||||
|
<th scope="col">Latest</th>
|
||||||
|
<th scope="col">Author</th>
|
||||||
|
<th scope="col">FHIR</th>
|
||||||
|
<th scope="col">Versions</th>
|
||||||
|
<!-- <th scope="col">Canonical</th> -->
|
||||||
|
{# Add Dependencies header if needed based on your data #}
|
||||||
|
{# <th scope="col">Dependencies</th> #}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for pkg in packages %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<i class="bi bi-box-seam me-2"></i>
|
||||||
|
{# Link to package details page - adjust if endpoint name is different #}
|
||||||
|
<a class="text-primary"
|
||||||
|
href="{{ url_for('package_details_view', name=pkg.name) }}"
|
||||||
|
{# Optional: Add HTMX GET for inline details if desired #}
|
||||||
|
{# hx-get="{{ url_for('package_details', name=pkg.name) }}" #}
|
||||||
|
{# hx-target="#search-results" #}
|
||||||
|
>
|
||||||
|
{{ pkg.name }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>{{ pkg.display_version }}</td>
|
||||||
|
<td>{{ pkg.author or '' }}</td>
|
||||||
|
<td>{{ pkg.fhir_version or '' }}</td>
|
||||||
|
<td>{{ pkg.version_count }}</td>
|
||||||
|
<!-- <td>{{ pkg.canonical or '' }}</td> -->
|
||||||
|
{# Add Dependencies data if needed #}
|
||||||
|
{# <td>{{ pkg.dependencies | join(', ') if pkg.dependencies else '' }}</td> #}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{# Pagination Controls - Ensure 'pagination' object is passed from the route #}
|
||||||
|
{% if pagination and pagination.pages > 1 %}
|
||||||
|
<nav aria-label="Page navigation">
|
||||||
|
<ul class="pagination justify-content-center">
|
||||||
|
{# Previous Page Link #}
|
||||||
|
{% if pagination.has_prev %}
|
||||||
|
<li class="page-item">
|
||||||
|
{# Use hx-get for HTMX-powered pagination within the results area #}
|
||||||
|
<a class="page-link"
|
||||||
|
href="{{ url_for('search_and_import', page=pagination.prev_num, search=request.args.get('search', '')) }}" {# Keep standard link for non-JS fallback #}
|
||||||
|
hx-get="{{ url_for('api_search_packages', page=pagination.prev_num, search=request.args.get('search', '')) }}" {# HTMX target #}
|
||||||
|
hx-target="#search-results"
|
||||||
|
hx-indicator=".htmx-indicator"
|
||||||
|
aria-label="Previous">
|
||||||
|
<span aria-hidden="true">«</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="page-item disabled">
|
||||||
|
<span class="page-link">«</span>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Page Number Links #}
|
||||||
|
{% for p in pagination.iter_pages %} {# Iterate over the pre-computed list #}
|
||||||
|
{% if p %}
|
||||||
|
<li class="page-item {% if p == pagination.page %}active{% endif %}">
|
||||||
|
<a class="page-link"
|
||||||
|
href="{{ url_for('search_and_import', page=p, search=request.args.get('search', '')) }}"
|
||||||
|
hx-get="{{ url_for('api_search_packages', page=p, search=request.args.get('search', '')) }}"
|
||||||
|
hx-target="#search-results"
|
||||||
|
hx-indicator=".htmx-indicator">
|
||||||
|
{{ p }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
{# Ellipsis for skipped pages #}
|
||||||
|
<li class="page-item disabled"><span class="page-link">...</span></li>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{# Next Page Link #}
|
||||||
|
{% if pagination.has_next %}
|
||||||
|
<li class="page-item">
|
||||||
|
<a class="page-link"
|
||||||
|
href="{{ url_for('search_and_import', page=pagination.next_num, search=request.args.get('search', '')) }}"
|
||||||
|
hx-get="{{ url_for('api_search_packages', page=pagination.next_num, search=request.args.get('search', '')) }}"
|
||||||
|
hx-target="#search-results"
|
||||||
|
hx-indicator=".htmx-indicator"
|
||||||
|
aria-label="Next">
|
||||||
|
<span aria-hidden="true">»</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="page-item disabled">
|
||||||
|
<span class="page-link">»</span>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
{% endif %} {# End pagination nav #}
|
||||||
|
|
||||||
|
{% elif request and request.args.get('search') %}
|
||||||
|
{# Message when search term is present but no results found #}
|
||||||
|
<p class="text-muted text-center mt-3">No packages found matching your search term.</p>
|
||||||
|
{% else %}
|
||||||
|
{# Initial message before any search #}
|
||||||
|
<p class="text-muted text-center mt-3">Start typing in the search box above to find packages.</p>
|
||||||
|
{% endif %}
|
||||||
@ -19,11 +19,11 @@
|
|||||||
<p>Whether you're downloading the latest IG versions, checking compliance, converting resources to FHIR Shorthand (FSH), or pushing guides to a test server, this toolkit aims to be an essential companion.</p>
|
<p>Whether you're downloading the latest IG versions, checking compliance, converting resources to FHIR Shorthand (FSH), or pushing guides to a test server, this toolkit aims to be an essential companion.</p>
|
||||||
{% if app_mode == 'lite' %}
|
{% if app_mode == 'lite' %}
|
||||||
<div class="alert alert-info" role="alert">
|
<div class="alert alert-info" role="alert">
|
||||||
<i class="bi bi-info-circle-fill me-2"></i>You are currently running the <strong>Lite Version</strong> of the toolkit. This version excludes the built-in HAPI FHIR server and relies on external FHIR servers for functionalities like the API Explorer and Operations UI. Validation uses local StructureDefinition checks.
|
<i class="bi bi-info-circle-fill me-2"></i>You are currently running the <strong>Lite Version</strong> of the toolkit. This version excludes the built-in FHIR server depending and relies on external FHIR servers for functionalities like the API Explorer and Operations UI. Validation uses local StructureDefinition checks.
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="alert alert-success" role="alert">
|
<div class="alert alert-success" role="alert">
|
||||||
<i class="bi bi-check-circle-fill me-2"></i>You are currently running the <strong>Standalone Version</strong> of the toolkit, which includes a built-in HAPI FHIR server (accessible via the `/fhir` proxy) for local validation and exploration.
|
<i class="bi bi-check-circle-fill me-2"></i>You are currently running the <strong>Standalone Version</strong> of the toolkit, which includes a built-in FHIR server based on your build choice (accessible via the `/fhir` proxy) for local validation and exploration, if you configured the Server ENV variable this could also be whatever customer server you have pointed the local ENV variable at allowing you to use any fhir server of your choice.
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
@ -36,7 +36,7 @@
|
|||||||
<li><strong>FHIR Server Interaction:</strong>
|
<li><strong>FHIR Server Interaction:</strong>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Push processed IGs (including dependencies) to a target FHIR server with real-time console feedback.</li>
|
<li>Push processed IGs (including dependencies) to a target FHIR server with real-time console feedback.</li>
|
||||||
<li>Explore FHIR server capabilities and interact with resources using the "FHIR API Explorer" (GET/POST/PUT/DELETE) and "FHIR UI Operations" pages, supporting both the local HAPI server (in Standalone mode) and external servers.</li>
|
<li>Explore FHIR server capabilities and interact with resources using the "FHIR API Explorer" (GET/POST/PUT/DELETE) and "FHIR UI Operations" pages, supporting both the local server (in Standalone mode) and external servers.</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><strong>FHIR Shorthand (FSH) Conversion:</strong> Convert FHIR JSON or XML resources to FSH using the integrated GoFSH tool. Offers advanced options like context package selection, various output styles, FHIR version selection, dependency loading, alias file usage, and round-trip validation ("Fishing Trip") with SUSHI. Includes a loading indicator during conversion.</li>
|
<li><strong>FHIR Shorthand (FSH) Conversion:</strong> Convert FHIR JSON or XML resources to FSH using the integrated GoFSH tool. Offers advanced options like context package selection, various output styles, FHIR version selection, dependency loading, alias file usage, and round-trip validation ("Fishing Trip") with SUSHI. Includes a loading indicator during conversion.</li>
|
||||||
@ -48,7 +48,7 @@
|
|||||||
<ul>
|
<ul>
|
||||||
<li><strong>Backend:</strong> Python with the Flask web framework and SQLAlchemy for database interaction (SQLite).</li>
|
<li><strong>Backend:</strong> Python with the Flask web framework and SQLAlchemy for database interaction (SQLite).</li>
|
||||||
<li><strong>Frontend:</strong> HTML, Bootstrap 5 for styling, and JavaScript for interactivity and dynamic content loading. Uses Lottie-Web for animations.</li>
|
<li><strong>Frontend:</strong> HTML, Bootstrap 5 for styling, and JavaScript for interactivity and dynamic content loading. Uses Lottie-Web for animations.</li>
|
||||||
<li><strong>FHIR Tooling:</strong> Integrates GoFSH and SUSHI (via Node.js) for FSH conversion and validation. Utilizes the HAPI FHIR server (in Standalone mode) for robust FHIR validation and operations.</li>
|
<li><strong>FHIR Tooling:</strong> Integrates GoFSH and SUSHI (via Node.js) for FSH conversion and validation. Utilizes the Local FHIR server (in Standalone mode) for robust FHIR validation and operations.</li>
|
||||||
<li><strong>Deployment:</strong> Runs within a Docker container managed by Docker Compose and Supervisor, ensuring a consistent environment.</li>
|
<li><strong>Deployment:</strong> Runs within a Docker container managed by Docker Compose and Supervisor, ensuring a consistent environment.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|||||||
@ -1,15 +1,22 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en" data-theme="{% if request.cookies.get('theme') == 'dark' %}dark{% else %}light{% endif %}">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css" integrity="sha512-z3gLpd7yknf1YoNbCzqRKc4qyor8gaKU1qmn+CShxbuBusANI9QpRohGBreCFkKxLhei6S9CQXFEbbKuqLg0DA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css" xintegrity="sha512-z3gLpd7yknf1YoNbCzqRKc4qyor8gaKU1qmn+CShxbuBusANI9QpRohGBreCFkKxLhei6S9CQXFEbbKuqLg0DA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-okaidia.min.css" rel="stylesheet" />
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-okaidia.min.css" rel="stylesheet" />
|
||||||
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/fire-animation.css') }}">
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/animation.css') }}">
|
||||||
|
<script src="{{ url_for('static', filename='js/htmx.min.js') }}"></script>
|
||||||
<title>{% if app_mode == 'lite' %}(Lite Version) {% endif %}{% if title %}{{ title }} - {% endif %}{{ site_name }}</title>
|
<title>{% if app_mode == 'lite' %}(Lite Version) {% endif %}{% if title %}{{ title }} - {% endif %}{{ site_name }}</title>
|
||||||
<style>
|
<style>
|
||||||
|
/* Prevent flash of unstyled content */
|
||||||
|
:root { visibility: hidden; }
|
||||||
|
[data-theme="dark"], [data-theme="light"] { visibility: visible; }
|
||||||
|
|
||||||
/* Default (Light Theme) Styles */
|
/* Default (Light Theme) Styles */
|
||||||
body {
|
body {
|
||||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
@ -177,44 +184,44 @@
|
|||||||
background: inherit; /* Ensure background comes from pre or theme */
|
background: inherit; /* Ensure background comes from pre or theme */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* General Button Theme (Primary, Secondary) */
|
/* General Button Theme (Primary, Secondary) */
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
background-color: #007bff;
|
background-color: #007bff;
|
||||||
border-color: #007bff;
|
border-color: #007bff;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
.btn-primary:hover {
|
.btn-primary:hover {
|
||||||
background-color: #0056b3;
|
background-color: #0056b3;
|
||||||
border-color: #004085;
|
border-color: #004085;
|
||||||
}
|
}
|
||||||
.btn-outline-primary {
|
.btn-outline-primary {
|
||||||
color: #007bff;
|
color: #007bff;
|
||||||
border-color: #007bff;
|
border-color: #007bff;
|
||||||
}
|
}
|
||||||
.btn-outline-primary:hover {
|
.btn-outline-primary:hover {
|
||||||
background-color: #007bff;
|
background-color: #007bff;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-secondary {
|
.btn-secondary {
|
||||||
background-color: #6c757d;
|
background-color: #6c757d;
|
||||||
border-color: #6c757d;
|
border-color: #6c757d;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
.btn-secondary:hover {
|
.btn-secondary:hover {
|
||||||
background-color: #5a6268;
|
background-color: #5a6268;
|
||||||
border-color: #545b62;
|
border-color: #545b62;
|
||||||
}
|
}
|
||||||
.btn-outline-secondary {
|
.btn-outline-secondary {
|
||||||
color: #6c757d;
|
color: #6c757d;
|
||||||
border-color: #6c757d;
|
border-color: #6c757d;
|
||||||
}
|
}
|
||||||
.btn-outline-secondary:hover {
|
.btn-outline-secondary:hover {
|
||||||
background-color: #6c757d;
|
background-color: #6c757d;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Prism.js Theme Overrides for Light Mode --- */
|
/* --- Prism.js Theme Overrides for Light Mode --- */
|
||||||
|
|
||||||
/* Target code blocks only when NOT in dark theme */
|
/* Target code blocks only when NOT in dark theme */
|
||||||
html:not([data-theme="dark"]) .token.punctuation,
|
html:not([data-theme="dark"]) .token.punctuation,
|
||||||
@ -256,380 +263,377 @@
|
|||||||
|
|
||||||
/* --- End Prism.js Overrides --- */
|
/* --- End Prism.js Overrides --- */
|
||||||
|
|
||||||
/* --- Theme Styles for FSH Code Blocks --- */
|
/* --- Theme Styles for FSH Code Blocks --- */
|
||||||
pre, pre code {
|
pre, pre code {
|
||||||
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
font-size: 0.875em;
|
font-size: 0.875em;
|
||||||
white-space: pre-wrap; /* Allow wrapping */
|
white-space: pre-wrap; /* Allow wrapping */
|
||||||
word-break: break-all; /* Break long lines */
|
word-break: break-all; /* Break long lines */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Target pre blocks specifically used for FSH output if needed */
|
/* Target pre blocks specifically used for FSH output if needed */
|
||||||
/* If the pre block has a specific class or ID, use that */
|
/* If the pre block has a specific class or ID, use that */
|
||||||
#fsh-output pre, /* Targets any pre within the output container */
|
#fsh-output pre, /* Targets any pre within the output container */
|
||||||
._fsh_output pre /* Targets any pre within the partial template content */
|
._fsh_output pre /* Targets any pre within the partial template content */
|
||||||
{
|
{
|
||||||
background-color: #e9ecef; /* Light theme background */
|
background-color: #e9ecef; /* Light theme background */
|
||||||
color: #212529; /* Light theme text */
|
color: #212529; /* Light theme text */
|
||||||
border: 1px solid #dee2e6;
|
border: 1px solid #dee2e6;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
max-height: 600px; /* Adjust as needed */
|
max-height: 600px; /* Adjust as needed */
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#fsh-output pre code,
|
#fsh-output pre code,
|
||||||
._fsh_output pre code {
|
._fsh_output pre code {
|
||||||
background-color: transparent !important; /* Ensure code tag doesn't override pre bg */
|
background-color: transparent !important; /* Ensure code tag doesn't override pre bg */
|
||||||
padding: 0;
|
padding: 0;
|
||||||
color: inherit; /* Inherit text color from pre */
|
color: inherit; /* Inherit text color from pre */
|
||||||
font-size: inherit; /* Inherit font size from pre */
|
font-size: inherit; /* Inherit font size from pre */
|
||||||
display: block; /* Make code fill pre */
|
display: block; /* Make code fill pre */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Dark Theme Override */
|
||||||
|
html[data-theme="dark"] #fsh-output pre,
|
||||||
|
html[data-theme="dark"] ._fsh_output pre {
|
||||||
|
background-color: #495057; /* Dark theme background */
|
||||||
|
color: #f8f9fa; /* Dark theme text */
|
||||||
|
border-color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
/* Dark Theme Override */
|
html[data-theme="dark"] #fsh-output pre code,
|
||||||
html[data-theme="dark"] #fsh-output pre,
|
html[data-theme="dark"] ._fsh_output pre code {
|
||||||
html[data-theme="dark"] ._fsh_output pre
|
color: inherit;
|
||||||
{
|
}
|
||||||
background-color: #495057; /* Dark theme background */
|
|
||||||
color: #f8f9fa; /* Dark theme text */
|
|
||||||
border-color: #6c757d;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="dark"] #fsh-output pre code,
|
/* --- Theme Styles for FHIR UI Operations (fhir_ui_operations.html) --- */
|
||||||
html[data-theme="dark"] ._fsh_output pre code {
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Theme Styles for FHIR UI Operations (fhir_ui_operations.html) --- */
|
/* Operation Block Container */
|
||||||
|
.opblock {
|
||||||
|
border: 1px solid #dee2e6; /* Light theme border */
|
||||||
|
background: #ffffff; /* Light theme background */
|
||||||
|
color: #212529; /* Light theme text */
|
||||||
|
}
|
||||||
|
html[data-theme="dark"] .opblock {
|
||||||
|
border-color: #495057; /* Dark theme border */
|
||||||
|
background: #343a40; /* Dark theme background (card bg) */
|
||||||
|
color: #f8f9fa; /* Dark theme text */
|
||||||
|
}
|
||||||
|
|
||||||
/* Operation Block Container */
|
/* Operation Summary (Header) */
|
||||||
.opblock {
|
.opblock-summary {
|
||||||
border: 1px solid #dee2e6; /* Light theme border */
|
background: #f8f9fa; /* Light theme summary bg */
|
||||||
background: #ffffff; /* Light theme background */
|
border-bottom: 1px solid #dee2e6;
|
||||||
color: #212529; /* Light theme text */
|
color: #212529; /* Default text color for summary */
|
||||||
}
|
}
|
||||||
html[data-theme="dark"] .opblock {
|
.opblock-summary:hover {
|
||||||
border-color: #495057; /* Dark theme border */
|
background: #e9ecef;
|
||||||
background: #343a40; /* Dark theme background (card bg) */
|
}
|
||||||
color: #f8f9fa; /* Dark theme text */
|
html[data-theme="dark"] .opblock-summary {
|
||||||
}
|
background: #40464c; /* Darker summary bg */
|
||||||
|
border-bottom-color: #495057;
|
||||||
|
color: #f8f9fa;
|
||||||
|
}
|
||||||
|
html[data-theme="dark"] .opblock-summary:hover {
|
||||||
|
background: #495057;
|
||||||
|
}
|
||||||
|
/* Keep method badge colors fixed - DO NOT override .opblock-summary-method */
|
||||||
|
.opblock-summary-description {
|
||||||
|
color: #495057; /* Slightly muted description text */
|
||||||
|
}
|
||||||
|
html[data-theme="dark"] .opblock-summary-description {
|
||||||
|
color: #adb5bd; /* Lighter muted text for dark */
|
||||||
|
}
|
||||||
|
|
||||||
/* Operation Summary (Header) */
|
/* Operation Body (Expanded Section) */
|
||||||
.opblock-summary {
|
.opblock-body {
|
||||||
background: #f8f9fa; /* Light theme summary bg */
|
border-top: 1px solid #dee2e6;
|
||||||
border-bottom: 1px solid #dee2e6;
|
}
|
||||||
color: #212529; /* Default text color for summary */
|
html[data-theme="dark"] .opblock-body {
|
||||||
}
|
border-top-color: #495057;
|
||||||
.opblock-summary:hover {
|
}
|
||||||
background: #e9ecef;
|
|
||||||
}
|
|
||||||
html[data-theme="dark"] .opblock-summary {
|
|
||||||
background: #40464c; /* Darker summary bg */
|
|
||||||
border-bottom-color: #495057;
|
|
||||||
color: #f8f9fa;
|
|
||||||
}
|
|
||||||
html[data-theme="dark"] .opblock-summary:hover {
|
|
||||||
background: #495057;
|
|
||||||
}
|
|
||||||
/* Keep method badge colors fixed - DO NOT override .opblock-summary-method */
|
|
||||||
.opblock-summary-description {
|
|
||||||
color: #495057; /* Slightly muted description text */
|
|
||||||
}
|
|
||||||
html[data-theme="dark"] .opblock-summary-description {
|
|
||||||
color: #adb5bd; /* Lighter muted text for dark */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Operation Body (Expanded Section) */
|
/* Section Headers within Body */
|
||||||
.opblock-body {
|
.opblock-section-header {
|
||||||
border-top: 1px solid #dee2e6;
|
border-bottom: 1px solid #eee;
|
||||||
}
|
color: inherit; /* Inherit from .opblock */
|
||||||
html[data-theme="dark"] .opblock-body {
|
}
|
||||||
border-top-color: #495057;
|
html[data-theme="dark"] .opblock-section-header {
|
||||||
}
|
border-bottom-color: #495057;
|
||||||
|
}
|
||||||
|
.opblock-title {
|
||||||
|
color: inherit; /* Inherit from .opblock */
|
||||||
|
}
|
||||||
|
|
||||||
/* Section Headers within Body */
|
/* Parameters Table */
|
||||||
.opblock-section-header {
|
.parameters-table th,
|
||||||
border-bottom: 1px solid #eee;
|
.parameters-table td {
|
||||||
color: inherit; /* Inherit from .opblock */
|
border: 1px solid #dee2e6;
|
||||||
}
|
color: inherit; /* Inherit from .opblock */
|
||||||
html[data-theme="dark"] .opblock-section-header {
|
}
|
||||||
border-bottom-color: #495057;
|
html[data-theme="dark"] .parameters-table th,
|
||||||
}
|
html[data-theme="dark"] .parameters-table td {
|
||||||
.opblock-title {
|
border-color: #495057;
|
||||||
color: inherit; /* Inherit from .opblock */
|
}
|
||||||
}
|
.parameter__type,
|
||||||
|
.parameter__in {
|
||||||
|
color: #6c757d; /* Use standard muted color */
|
||||||
|
}
|
||||||
|
html[data-theme="dark"] .parameter__type,
|
||||||
|
html[data-theme="dark"] .parameter__in {
|
||||||
|
color: #adb5bd; /* Use standard dark muted color */
|
||||||
|
}
|
||||||
|
.parameters-col_description input[type="text"],
|
||||||
|
.parameters-col_description input[type="number"] {
|
||||||
|
background-color: #fff; /* Match light form controls */
|
||||||
|
border: 1px solid #ced4da;
|
||||||
|
color: #212529;
|
||||||
|
}
|
||||||
|
html[data-theme="dark"] .parameters-col_description input[type="text"],
|
||||||
|
html[data-theme="dark"] .parameters-col_description input[type="number"] {
|
||||||
|
background-color: #495057; /* Match dark form controls */
|
||||||
|
border-color: #6c757d;
|
||||||
|
color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
/* Parameters Table */
|
/* Request Body Textarea */
|
||||||
.parameters-table th,
|
.request-body-textarea {
|
||||||
.parameters-table td {
|
background-color: #fff; /* Match light form controls */
|
||||||
border: 1px solid #dee2e6;
|
border: 1px solid #ced4da;
|
||||||
color: inherit; /* Inherit from .opblock */
|
color: #212529;
|
||||||
}
|
}
|
||||||
html[data-theme="dark"] .parameters-table th,
|
html[data-theme="dark"] .request-body-textarea {
|
||||||
html[data-theme="dark"] .parameters-table td {
|
background-color: #495057; /* Match dark form controls */
|
||||||
border-color: #495057;
|
border-color: #6c757d;
|
||||||
}
|
color: #f8f9fa;
|
||||||
.parameter__type,
|
}
|
||||||
.parameter__in {
|
|
||||||
color: #6c757d; /* Use standard muted color */
|
|
||||||
}
|
|
||||||
html[data-theme="dark"] .parameter__type,
|
|
||||||
html[data-theme="dark"] .parameter__in {
|
|
||||||
color: #adb5bd; /* Use standard dark muted color */
|
|
||||||
}
|
|
||||||
.parameters-col_description input[type="text"],
|
|
||||||
.parameters-col_description input[type="number"] {
|
|
||||||
background-color: #fff; /* Match light form controls */
|
|
||||||
border: 1px solid #ced4da;
|
|
||||||
color: #212529;
|
|
||||||
}
|
|
||||||
html[data-theme="dark"] .parameters-col_description input[type="text"],
|
|
||||||
html[data-theme="dark"] .parameters-col_description input[type="number"] {
|
|
||||||
background-color: #495057; /* Match dark form controls */
|
|
||||||
border-color: #6c757d;
|
|
||||||
color: #f8f9fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Request Body Textarea */
|
/* Responses Table */
|
||||||
.request-body-textarea {
|
.responses-table th,
|
||||||
background-color: #fff; /* Match light form controls */
|
.responses-table td {
|
||||||
border: 1px solid #ced4da;
|
border: 1px solid #dee2e6;
|
||||||
color: #212529;
|
color: inherit;
|
||||||
}
|
}
|
||||||
html[data-theme="dark"] .request-body-textarea {
|
html[data-theme="dark"] .responses-table th,
|
||||||
background-color: #495057; /* Match dark form controls */
|
html[data-theme="dark"] .responses-table td {
|
||||||
border-color: #6c757d;
|
border-color: #495057;
|
||||||
color: #f8f9fa;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* Responses Table */
|
/* Example/Schema Tabs & Controls */
|
||||||
.responses-table th,
|
.response-control-media-type {
|
||||||
.responses-table td {
|
background-color: #f8f9fa;
|
||||||
border: 1px solid #dee2e6;
|
}
|
||||||
color: inherit;
|
html[data-theme="dark"] .response-control-media-type {
|
||||||
}
|
background-color: #40464c; /* Match summary header bg */
|
||||||
html[data-theme="dark"] .responses-table th,
|
}
|
||||||
html[data-theme="dark"] .responses-table td {
|
.tablinks.badge { /* Default inactive tab */
|
||||||
border-color: #495057;
|
background: #dee2e6 !important; /* Light grey inactive */
|
||||||
}
|
color: #333 !important;
|
||||||
|
}
|
||||||
|
.tablinks.badge.active { /* Default active tab */
|
||||||
|
background: #007bff !important; /* Use primary color */
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
html[data-theme="dark"] .tablinks.badge { /* Dark inactive tab */
|
||||||
|
background: #6c757d !important; /* Dark grey inactive */
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
html[data-theme="dark"] .tablinks.badge.active { /* Dark active tab */
|
||||||
|
background: #4dabf7 !important; /* Use dark primary color */
|
||||||
|
color: #000 !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* Example/Schema Tabs & Controls */
|
/* Code/Output Blocks */
|
||||||
.response-control-media-type {
|
.highlight-code,
|
||||||
background-color: #f8f9fa;
|
.request-url-output,
|
||||||
}
|
.curl-output,
|
||||||
html[data-theme="dark"] .response-control-media-type {
|
.execute-wrapper pre.response-output-content {
|
||||||
background-color: #40464c; /* Match summary header bg */
|
background: #e9ecef; /* Light theme pre background */
|
||||||
}
|
color: #212529;
|
||||||
.tablinks.badge { /* Default inactive tab */
|
border: 1px solid #dee2e6;
|
||||||
background: #dee2e6 !important; /* Light grey inactive */
|
}
|
||||||
color: #333 !important;
|
html[data-theme="dark"] .highlight-code,
|
||||||
}
|
html[data-theme="dark"] .request-url-output,
|
||||||
.tablinks.badge.active { /* Default active tab */
|
html[data-theme="dark"] .curl-output,
|
||||||
background: #007bff !important; /* Use primary color */
|
html[data-theme="dark"] .execute-wrapper pre.response-output-content {
|
||||||
color: white !important;
|
background: #495057; /* Dark theme pre background */
|
||||||
}
|
color: #f8f9fa;
|
||||||
html[data-theme="dark"] .tablinks.badge { /* Dark inactive tab */
|
border-color: #6c757d;
|
||||||
background: #6c757d !important; /* Dark grey inactive */
|
}
|
||||||
color: white !important;
|
|
||||||
}
|
|
||||||
html[data-theme="dark"] .tablinks.badge.active { /* Dark active tab */
|
|
||||||
background: #4dabf7 !important; /* Use dark primary color */
|
|
||||||
color: #000 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Code/Output Blocks */
|
/* Narrative Response Box */
|
||||||
.highlight-code,
|
.execute-wrapper div.response-output-narrative {
|
||||||
.request-url-output,
|
background: #ffffff;
|
||||||
.curl-output,
|
color: #212529;
|
||||||
.execute-wrapper pre.response-output-content {
|
border: 1px solid #dee2e6;
|
||||||
background: #e9ecef; /* Light theme pre background */
|
}
|
||||||
color: #212529;
|
html[data-theme="dark"] .execute-wrapper div.response-output-narrative {
|
||||||
border: 1px solid #dee2e6;
|
background: #343a40; /* Match card body */
|
||||||
}
|
color: #f8f9fa;
|
||||||
html[data-theme="dark"] .highlight-code,
|
border: 1px solid #495057;
|
||||||
html[data-theme="dark"] .request-url-output,
|
}
|
||||||
html[data-theme="dark"] .curl-output,
|
|
||||||
html[data-theme="dark"] .execute-wrapper pre.response-output-content {
|
|
||||||
background: #495057; /* Dark theme pre background */
|
|
||||||
color: #f8f9fa;
|
|
||||||
border-color: #6c757d;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Narrative Response Box */
|
/* Response Status Text */
|
||||||
.execute-wrapper div.response-output-narrative {
|
.execute-wrapper .response-status[style*="color: green"] { color: #198754 !important; }
|
||||||
background: #ffffff;
|
.execute-wrapper .response-status[style*="color: red"] { color: #dc3545 !important; }
|
||||||
color: #212529;
|
html[data-theme="dark"] .execute-wrapper .response-status[style*="color: green"] { color: #20c997 !important; }
|
||||||
border: 1px solid #dee2e6;
|
html[data-theme="dark"] .execute-wrapper .response-status[style*="color: red"] { color: #e63946 !important; }
|
||||||
}
|
|
||||||
html[data-theme="dark"] .execute-wrapper div.response-output-narrative {
|
|
||||||
background: #343a40; /* Match card body */
|
|
||||||
color: #f8f9fa;
|
|
||||||
border: 1px solid #495057;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Response Status Text */
|
/* Specific Action Buttons (Success, Danger, Warning, Info) */
|
||||||
.execute-wrapper .response-status[style*="color: green"] { color: #198754 !important; }
|
.btn-success { background-color: #198754; border-color: #198754; color: white; }
|
||||||
.execute-wrapper .response-status[style*="color: red"] { color: #dc3545 !important; }
|
.btn-success:hover { background-color: #157347; border-color: #146c43; }
|
||||||
html[data-theme="dark"] .execute-wrapper .response-status[style*="color: green"] { color: #20c997 !important; }
|
.btn-outline-success { color: #198754; border-color: #198754; }
|
||||||
html[data-theme="dark"] .execute-wrapper .response-status[style*="color: red"] { color: #e63946 !important; }
|
.btn-outline-success:hover { background-color: #198754; color: white; }
|
||||||
|
|
||||||
|
.btn-danger { background-color: #dc3545; border-color: #dc3545; color: white; }
|
||||||
|
.btn-danger:hover { background-color: #bb2d3b; border-color: #b02a37; }
|
||||||
|
.btn-outline-danger { color: #dc3545; border-color: #dc3545; }
|
||||||
|
.btn-outline-danger:hover { background-color: #dc3545; color: white; }
|
||||||
|
|
||||||
/* Specific Action Buttons (Success, Danger, Warning, Info) */
|
.btn-warning { background-color: #ffc107; border-color: #ffc107; color: #000; }
|
||||||
.btn-success { background-color: #198754; border-color: #198754; color: white; }
|
.btn-warning:hover { background-color: #ffca2c; border-color: #ffc720; }
|
||||||
.btn-success:hover { background-color: #157347; border-color: #146c43; }
|
.btn-outline-warning { color: #ffc107; border-color: #ffc107; }
|
||||||
.btn-outline-success { color: #198754; border-color: #198754; }
|
.btn-outline-warning:hover { background-color: #ffc107; color: #000; }
|
||||||
.btn-outline-success:hover { background-color: #198754; color: white; }
|
|
||||||
|
|
||||||
.btn-danger { background-color: #dc3545; border-color: #dc3545; color: white; }
|
.btn-info { background-color: #0dcaf0; border-color: #0dcaf0; color: #000; }
|
||||||
.btn-danger:hover { background-color: #bb2d3b; border-color: #b02a37; }
|
.btn-info:hover { background-color: #31d2f2; border-color: #25cff2; }
|
||||||
.btn-outline-danger { color: #dc3545; border-color: #dc3545; }
|
.btn-outline-info { color: #0dcaf0; border-color: #0dcaf0; }
|
||||||
.btn-outline-danger:hover { background-color: #dc3545; color: white; }
|
.btn-outline-info:hover { background-color: #0dcaf0; color: #000; }
|
||||||
|
|
||||||
.btn-warning { background-color: #ffc107; border-color: #ffc107; color: #000; }
|
/* General Badge Theme */
|
||||||
.btn-warning:hover { background-color: #ffca2c; border-color: #ffc720; }
|
.badge {
|
||||||
.btn-outline-warning { color: #ffc107; border-color: #ffc107; }
|
padding: 0.4em 0.6em; /* Slightly larger padding */
|
||||||
.btn-outline-warning:hover { background-color: #ffc107; color: #000; }
|
font-size: 0.85em;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
/* Specific Badge Backgrounds (Add more as needed) */
|
||||||
|
.badge.bg-primary { background-color: #007bff !important; color: white !important; }
|
||||||
|
.badge.bg-secondary { background-color: #6c757d !important; color: white !important; }
|
||||||
|
.badge.bg-success { background-color: #198754 !important; color: white !important; }
|
||||||
|
.badge.bg-danger { background-color: #dc3545 !important; color: white !important; }
|
||||||
|
.badge.bg-warning { background-color: #ffc107 !important; color: #000 !important; }
|
||||||
|
.badge.bg-info { background-color: #0dcaf0 !important; color: #000 !important; }
|
||||||
|
.badge.bg-light { background-color: #f8f9fa !important; color: #000 !important; border: 1px solid #dee2e6; }
|
||||||
|
.badge.bg-dark { background-color: #343a40 !important; color: white !important; }
|
||||||
|
|
||||||
.btn-info { background-color: #0dcaf0; border-color: #0dcaf0; color: #000; }
|
/* Copy Button Specific Style */
|
||||||
.btn-info:hover { background-color: #31d2f2; border-color: #25cff2; }
|
.btn-copy { /* Optional: Add a specific class if needed, or style .btn-outline-secondary directly */
|
||||||
.btn-outline-info { color: #0dcaf0; border-color: #0dcaf0; }
|
padding: 0.25rem 0.5rem;
|
||||||
.btn-outline-info:hover { background-color: #0dcaf0; color: #000; }
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
/* General Badge Theme */
|
/* --- Dark Theme Overrides --- */
|
||||||
.badge {
|
html[data-theme="dark"] .btn-primary { background-color: #4dabf7; border-color: #4dabf7; color: #000; }
|
||||||
padding: 0.4em 0.6em; /* Slightly larger padding */
|
html[data-theme="dark"] .btn-primary:hover { background-color: #68bcef; border-color: #5fb7ea; }
|
||||||
font-size: 0.85em;
|
html[data-theme="dark"] .btn-outline-primary { color: #4dabf7; border-color: #4dabf7; }
|
||||||
font-weight: 600;
|
html[data-theme="dark"] .btn-outline-primary:hover { background-color: #4dabf7; color: #000; }
|
||||||
}
|
|
||||||
/* Specific Badge Backgrounds (Add more as needed) */
|
|
||||||
.badge.bg-primary { background-color: #007bff !important; color: white !important; }
|
|
||||||
.badge.bg-secondary { background-color: #6c757d !important; color: white !important; }
|
|
||||||
.badge.bg-success { background-color: #198754 !important; color: white !important; }
|
|
||||||
.badge.bg-danger { background-color: #dc3545 !important; color: white !important; }
|
|
||||||
.badge.bg-warning { background-color: #ffc107 !important; color: #000 !important; }
|
|
||||||
.badge.bg-info { background-color: #0dcaf0 !important; color: #000 !important; }
|
|
||||||
.badge.bg-light { background-color: #f8f9fa !important; color: #000 !important; border: 1px solid #dee2e6; }
|
|
||||||
.badge.bg-dark { background-color: #343a40 !important; color: white !important; }
|
|
||||||
|
|
||||||
/* Copy Button Specific Style */
|
html[data-theme="dark"] .btn-secondary { background-color: #6c757d; border-color: #6c757d; color: white; }
|
||||||
.btn-copy { /* Optional: Add a specific class if needed, or style .btn-outline-secondary directly */
|
html[data-theme="dark"] .btn-secondary:hover { background-color: #5a6268; border-color: #545b62; }
|
||||||
padding: 0.25rem 0.5rem;
|
html[data-theme="dark"] .btn-outline-secondary { color: #adb5bd; border-color: #6c757d; } /* Lighter text/border */
|
||||||
font-size: 0.875rem;
|
html[data-theme="dark"] .btn-outline-secondary:hover { background-color: #6c757d; color: white; }
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Dark Theme Overrides --- */
|
html[data-theme="dark"] .btn-success { background-color: #20c997; border-color: #20c997; color: #000; }
|
||||||
html[data-theme="dark"] .btn-primary { background-color: #4dabf7; border-color: #4dabf7; color: #000; }
|
html[data-theme="dark"] .btn-success:hover { background-color: #3ce0ac; border-color: #36d8a3; }
|
||||||
html[data-theme="dark"] .btn-primary:hover { background-color: #68bcef; border-color: #5fb7ea; }
|
html[data-theme="dark"] .btn-outline-success { color: #20c997; border-color: #20c997; }
|
||||||
html[data-theme="dark"] .btn-outline-primary { color: #4dabf7; border-color: #4dabf7; }
|
html[data-theme="dark"] .btn-outline-success:hover { background-color: #20c997; color: #000; }
|
||||||
html[data-theme="dark"] .btn-outline-primary:hover { background-color: #4dabf7; color: #000; }
|
|
||||||
|
|
||||||
html[data-theme="dark"] .btn-secondary { background-color: #6c757d; border-color: #6c757d; color: white; }
|
/* Keep danger as is */
|
||||||
html[data-theme="dark"] .btn-secondary:hover { background-color: #5a6268; border-color: #545b62; }
|
html[data-theme="dark"] .btn-danger { background-color: #e63946; border-color: #e63946; color: white; }
|
||||||
html[data-theme="dark"] .btn-outline-secondary { color: #adb5bd; border-color: #6c757d; } /* Lighter text/border */
|
html[data-theme="dark"] .btn-danger:hover { background-color: #c82b39; border-color: #bd2130; }
|
||||||
html[data-theme="dark"] .btn-outline-secondary:hover { background-color: #6c757d; color: white; }
|
html[data-theme="dark"] .btn-outline-danger { color: #e63946; border-color: #e63946; }
|
||||||
|
html[data-theme="dark"] .btn-outline-danger:hover { background-color: #e63946; color: white; }
|
||||||
|
|
||||||
html[data-theme="dark"] .btn-success { background-color: #20c997; border-color: #20c997; color: #000; }
|
html[data-theme="dark"] .btn-warning { background-color: #ffca2c; border-color: #ffca2c; color: #000; }
|
||||||
html[data-theme="dark"] .btn-success:hover { background-color: #3ce0ac; border-color: #36d8a3; }
|
html[data-theme="dark"] .btn-warning:hover { background-color: #ffd24a; border-color: #ffce3d; }
|
||||||
html[data-theme="dark"] .btn-outline-success { color: #20c997; border-color: #20c997; }
|
html[data-theme="dark"] .btn-outline-warning { color: #ffca2c; border-color: #ffca2c; }
|
||||||
html[data-theme="dark"] .btn-outline-success:hover { background-color: #20c997; color: #000; }
|
html[data-theme="dark"] .btn-outline-warning:hover { background-color: #ffca2c; color: #000; }
|
||||||
|
|
||||||
/* Keep danger as is */
|
html[data-theme="dark"] .btn-info { background-color: #22b8cf; border-color: #22b8cf; color: #000; }
|
||||||
html[data-theme="dark"] .btn-danger { background-color: #e63946; border-color: #e63946; color: white; }
|
html[data-theme="dark"] .btn-info:hover { background-color: #44cee3; border-color: #38cae0; }
|
||||||
html[data-theme="dark"] .btn-danger:hover { background-color: #c82b39; border-color: #bd2130; }
|
html[data-theme="dark"] .btn-outline-info { color: #22b8cf; border-color: #22b8cf; }
|
||||||
html[data-theme="dark"] .btn-outline-danger { color: #e63946; border-color: #e63946; }
|
html[data-theme="dark"] .btn-outline-info:hover { background-color: #22b8cf; color: #000; }
|
||||||
html[data-theme="dark"] .btn-outline-danger:hover { background-color: #e63946; color: white; }
|
|
||||||
|
|
||||||
html[data-theme="dark"] .btn-warning { background-color: #ffca2c; border-color: #ffca2c; color: #000; }
|
/* Dark Theme Badges */
|
||||||
html[data-theme="dark"] .btn-warning:hover { background-color: #ffd24a; border-color: #ffce3d; }
|
html[data-theme="dark"] .badge.bg-primary { background-color: #4dabf7 !important; color: #000 !important; }
|
||||||
html[data-theme="dark"] .btn-outline-warning { color: #ffca2c; border-color: #ffca2c; }
|
html[data-theme="dark"] .badge.bg-secondary { background-color: #6c757d !important; color: white !important; }
|
||||||
html[data-theme="dark"] .btn-outline-warning:hover { background-color: #ffca2c; color: #000; }
|
html[data-theme="dark"] .badge.bg-success { background-color: #20c997 !important; color: #000 !important; }
|
||||||
|
html[data-theme="dark"] .badge.bg-danger { background-color: #e63946 !important; color: white !important; }
|
||||||
|
html[data-theme="dark"] .badge.bg-warning { background-color: #ffca2c !important; color: #000 !important; }
|
||||||
|
html[data-theme="dark"] .badge.bg-info { background-color: #22b8cf !important; color: #000 !important; }
|
||||||
|
html[data-theme="dark"] .badge.bg-light { background-color: #495057 !important; color: #f8f9fa !important; border: 1px solid #6c757d; }
|
||||||
|
html[data-theme="dark"] .badge.bg-dark { background-color: #adb5bd !important; color: #000 !important; }
|
||||||
|
|
||||||
html[data-theme="dark"] .btn-info { background-color: #22b8cf; border-color: #22b8cf; color: #000; }
|
/* --- Themed Backgrounds for Code/Structure View (cp_view_processed_ig.html) --- */
|
||||||
html[data-theme="dark"] .btn-info:hover { background-color: #44cee3; border-color: #38cae0; }
|
|
||||||
html[data-theme="dark"] .btn-outline-info { color: #22b8cf; border-color: #22b8cf; }
|
|
||||||
html[data-theme="dark"] .btn-outline-info:hover { background-color: #22b8cf; color: #000; }
|
|
||||||
|
|
||||||
/* Dark Theme Badges */
|
/* Styling for <pre> blocks containing code */
|
||||||
html[data-theme="dark"] .badge.bg-primary { background-color: #4dabf7 !important; color: #000 !important; }
|
#raw-structure-wrapper pre,
|
||||||
html[data-theme="dark"] .badge.bg-secondary { background-color: #6c757d !important; color: white !important; }
|
#example-content-wrapper pre {
|
||||||
html[data-theme="dark"] .badge.bg-success { background-color: #20c997 !important; color: #000 !important; }
|
background-color: #e9ecef; /* Light theme background */
|
||||||
html[data-theme="dark"] .badge.bg-danger { background-color: #e63946 !important; color: white !important; }
|
color: #212529; /* Light theme text */
|
||||||
html[data-theme="dark"] .badge.bg-warning { background-color: #ffca2c !important; color: #000 !important; }
|
border: 1px solid #dee2e6;
|
||||||
html[data-theme="dark"] .badge.bg-info { background-color: #22b8cf !important; color: #000 !important; }
|
border-radius: 4px;
|
||||||
html[data-theme="dark"] .badge.bg-light { background-color: #495057 !important; color: #f8f9fa !important; border: 1px solid #6c757d; }
|
padding: 10px;
|
||||||
html[data-theme="dark"] .badge.bg-dark { background-color: #adb5bd !important; color: #000 !important; }
|
max-height: 400px; /* Keep existing max-height */
|
||||||
|
overflow-y: auto; /* Keep existing overflow */
|
||||||
|
white-space: pre-wrap; /* Ensure wrapping */
|
||||||
|
word-break: break-all; /* Break long strings */
|
||||||
|
}
|
||||||
|
|
||||||
/* --- Themed Backgrounds for Code/Structure View (cp_view_processed_ig.html) --- */
|
/* Ensure code tag inside inherits background and has appropriate font */
|
||||||
|
#raw-structure-wrapper pre code,
|
||||||
|
#example-content-wrapper pre code {
|
||||||
|
background-color: transparent !important; /* Let pre handle background */
|
||||||
|
padding: 0;
|
||||||
|
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
|
font-size: 0.875em;
|
||||||
|
color: inherit; /* Inherit text color from pre */
|
||||||
|
display: block; /* Make code block fill pre */
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
/* Styling for <pre> blocks containing code */
|
/* Styling for the Structure Definition tree list items */
|
||||||
#raw-structure-wrapper pre,
|
.structure-tree-root .list-group-item {
|
||||||
#example-content-wrapper pre {
|
background-color: #ffffff; /* Light theme default background */
|
||||||
background-color: #e9ecef; /* Light theme background */
|
border-color: #dee2e6;
|
||||||
color: #212529; /* Light theme text */
|
color: #212529;
|
||||||
border: 1px solid #dee2e6;
|
}
|
||||||
border-radius: 4px;
|
|
||||||
padding: 10px;
|
|
||||||
max-height: 400px; /* Keep existing max-height */
|
|
||||||
overflow-y: auto; /* Keep existing overflow */
|
|
||||||
white-space: pre-wrap; /* Ensure wrapping */
|
|
||||||
word-break: break-all; /* Break long strings */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure code tag inside inherits background and has appropriate font */
|
.structure-tree-root .list-group-item-warning {
|
||||||
#raw-structure-wrapper pre code,
|
background-color: #fff3cd; /* Bootstrap light theme warning */
|
||||||
#example-content-wrapper pre code {
|
border-color: #ffeeba;
|
||||||
background-color: transparent !important; /* Let pre handle background */
|
/*color: #856404; Ensure text is readable */
|
||||||
padding: 0;
|
}
|
||||||
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
||||||
font-size: 0.875em;
|
|
||||||
color: inherit; /* Inherit text color from pre */
|
|
||||||
display: block; /* Make code block fill pre */
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Styling for the Structure Definition tree list items */
|
/* --- Dark Theme Overrides for Code/Structure View --- */
|
||||||
.structure-tree-root .list-group-item {
|
html[data-theme="dark"] #raw-structure-wrapper pre,
|
||||||
background-color: #ffffff; /* Light theme default background */
|
html[data-theme="dark"] #example-content-wrapper pre {
|
||||||
border-color: #dee2e6;
|
background-color: #495057; /* Dark theme background */
|
||||||
color: #212529;
|
color: #f8f9fa; /* Dark theme text */
|
||||||
}
|
border-color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
.structure-tree-root .list-group-item-warning {
|
/* Dark theme code text color already inherited, but explicit doesn't hurt */
|
||||||
background-color: #fff3cd; /* Bootstrap light theme warning */
|
html[data-theme="dark"] #raw-structure-wrapper pre code,
|
||||||
border-color: #ffeeba;
|
html[data-theme="dark"] #example-content-wrapper pre code {
|
||||||
/*color: #856404; Ensure text is readable */
|
color: #f8f9fa;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Dark Theme Overrides for Code/Structure View --- */
|
/* Dark theme structure list items */
|
||||||
html[data-theme="dark"] #raw-structure-wrapper pre,
|
html[data-theme="dark"] .structure-tree-root .list-group-item {
|
||||||
html[data-theme="dark"] #example-content-wrapper pre {
|
background-color: #343a40; /* Dark theme card background */
|
||||||
background-color: #495057; /* Dark theme background */
|
border-color: #495057;
|
||||||
color: #f8f9fa; /* Dark theme text */
|
color: #f8f9fa;
|
||||||
border-color: #6c757d;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* Dark theme code text color already inherited, but explicit doesn't hurt */
|
html[data-theme="dark"] .structure-tree-root .list-group-item-warning {
|
||||||
html[data-theme="dark"] #raw-structure-wrapper pre code,
|
background-color: #664d03; /* Darker warning background */
|
||||||
html[data-theme="dark"] #example-content-wrapper pre code {
|
border-color: #997404;
|
||||||
color: #f8f9fa;
|
/*color: #ffecb5; Lighter text for contrast */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dark theme structure list items */
|
|
||||||
html[data-theme="dark"] .structure-tree-root .list-group-item {
|
|
||||||
background-color: #343a40; /* Dark theme card background */
|
|
||||||
border-color: #495057;
|
|
||||||
color: #f8f9fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="dark"] .structure-tree-root .list-group-item-warning {
|
|
||||||
background-color: #664d03; /* Darker warning background */
|
|
||||||
border-color: #997404;
|
|
||||||
/*color: #ffecb5; Lighter text for contrast */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dark Theme Styles */
|
/* Dark Theme Styles */
|
||||||
html[data-theme="dark"] body {
|
html[data-theme="dark"] body {
|
||||||
@ -750,7 +754,7 @@ html[data-theme="dark"] .structure-tree-root .list-group-item-warning {
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="d-flex flex-column min-vh-100">
|
<body class="d-flex flex-column min-vh-100 {% block body_class %}{% endblock %}">
|
||||||
<nav class="navbar navbar-expand-lg navbar-light">
|
<nav class="navbar navbar-expand-lg navbar-light">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="navbar-brand" href="{{ url_for('index') }}">{{ site_name }}{% if app_mode == 'lite' %} (Lite){% endif %}</a>
|
<a class="navbar-brand" href="{{ url_for('index') }}">{{ site_name }}{% if app_mode == 'lite' %} (Lite){% endif %}</a>
|
||||||
@ -763,27 +767,44 @@ html[data-theme="dark"] .structure-tree-root .list-group-item-warning {
|
|||||||
<a class="nav-link {{ 'active' if request.endpoint == 'index' else '' }}" aria-current="page" href="{{ url_for('index') }}"><i class="fas fa-house me-1"></i> Home</a>
|
<a class="nav-link {{ 'active' if request.endpoint == 'index' else '' }}" aria-current="page" href="{{ url_for('index') }}"><i class="fas fa-house me-1"></i> Home</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {% if request.path == '/search-and-import' %}active{% endif %}" href="{{ url_for('search_and_import') }}"><i class="fas fa-download me-1"></i> Search and Import</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {{ 'active' if request.endpoint == 'manual_import_ig' else '' }}" href="{{ url_for('manual_import_ig') }}"><i class="fas fa-folder-open me-1"></i> Manual Import</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
<a class="nav-link {{ 'active' if request.endpoint == 'view_igs' else '' }}" href="{{ url_for('view_igs') }}"><i class="fas fa-folder-open me-1"></i> Manage FHIR Packages</a>
|
<a class="nav-link {{ 'active' if request.endpoint == 'view_igs' else '' }}" href="{{ url_for('view_igs') }}"><i class="fas fa-folder-open me-1"></i> Manage FHIR Packages</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link {{ 'active' if request.endpoint == 'import_ig' else '' }}" href="{{ url_for('import_ig') }}"><i class="fas fa-download me-1"></i> Import IGs</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link {{ 'active' if request.endpoint == 'push_igs' else '' }}" href="{{ url_for('push_igs') }}"><i class="fas fa-upload me-1"></i> Push IGs</a>
|
<a class="nav-link {{ 'active' if request.endpoint == 'push_igs' else '' }}" href="{{ url_for('push_igs') }}"><i class="fas fa-upload me-1"></i> Push IGs</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {{ 'active' if request.endpoint == 'upload_test_data' else '' }}" href="{{ url_for('upload_test_data') }}"><i class="bi bi-clipboard-data me-1"></i>Upload Test Data</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link {{ 'active' if request.endpoint == 'validate_sample' else '' }}" href="{{ url_for('validate_sample') }}"><i class="fas fa-check-circle me-1"></i> Validate FHIR Sample</a>
|
<a class="nav-link {{ 'active' if request.endpoint == 'validate_sample' else '' }}" href="{{ url_for('validate_sample') }}"><i class="fas fa-check-circle me-1"></i> Validate FHIR Sample</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {{ 'active' if request.endpoint == 'retrieve_split_data' else '' }}" href="{{ url_for('retrieve_split_data') }}"><i class="fas bi-cloud-download me-1"></i> Retrieve & Split Data</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link {{ 'active' if request.endpoint == 'fhir_ui' else '' }}" href="{{ url_for('fhir_ui') }}"><i class="fas fa-cloud-download-alt me-1"></i> FHIR API Explorer</a>
|
<a class="nav-link {{ 'active' if request.endpoint == 'fhir_ui' else '' }}" href="{{ url_for('fhir_ui') }}"><i class="fas fa-cloud-download-alt me-1"></i> FHIR API Explorer</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link {{ 'active' if request.endpoint == 'fhir_ui_operations' else '' }}" href="{{ url_for('fhir_ui_operations') }}"><i class="bi bi-gear me-1"></i> FHIR UI Operations</a>
|
<a class="nav-link {{ 'active' if request.endpoint == 'fhir_ui_operations' else '' }}" href="{{ url_for('fhir_ui_operations') }}"><i class="bi bi-gear me-1"></i> FHIR UI Operations</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link {{ 'active' if request.endpoint == 'fsh_converter' else '' }}" href="{{ url_for('fsh_converter') }}"><i class="fas fa-file-code me-1"></i> FSH Converter</a>
|
<a class="nav-link {{ 'active' if request.endpoint == 'fsh_converter' else '' }}" href="{{ url_for('fsh_converter') }}"><i class="fas fa-file-code me-1"></i> FSH Converter</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
<!-- <li class="nav-item">
|
||||||
|
<a class="nav-link {{ 'active' if request.endpoint == 'ig_configurator' else '' }}" href="{{ url_for('ig_configurator') }}"><i class="fas fa-wrench me-1"></i> IG Valid-Conf Gen</a>
|
||||||
|
</li> -->
|
||||||
|
<!-- {% if app_mode != 'lite' %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {{ 'active' if request.endpoint == 'config_hapi' else '' }}" href="{{ url_for('config_hapi') }}"><i class="fas fa-cog me-1"></i> Configure HAPI</a>
|
||||||
|
</li>
|
||||||
|
{% endif %} -->
|
||||||
|
</ul>
|
||||||
<div class="navbar-controls d-flex align-items-center">
|
<div class="navbar-controls d-flex align-items-center">
|
||||||
<div class="form-check form-switch">
|
<div class="form-check form-switch">
|
||||||
<input class="form-check-input" type="checkbox" id="themeToggle" onchange="toggleTheme()" aria-label="Toggle dark mode" {% if request.cookies.get('theme') == 'dark' %}checked{% endif %}>
|
<input class="form-check-input" type="checkbox" id="themeToggle" onchange="toggleTheme()" aria-label="Toggle dark mode" {% if request.cookies.get('theme') == 'dark' %}checked{% endif %}>
|
||||||
@ -799,7 +820,6 @@ html[data-theme="dark"] .structure-tree-root .list-group-item-warning {
|
|||||||
|
|
||||||
<main class="flex-grow-1">
|
<main class="flex-grow-1">
|
||||||
<div class="container mt-4">
|
<div class="container mt-4">
|
||||||
<!-- Flashed Messages Section -->
|
|
||||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
@ -834,6 +854,7 @@ html[data-theme="dark"] .structure-tree-root .list-group-item-warning {
|
|||||||
<a href="https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit/issues/new/choose" class="text-danger text-decoration-none" aria-label="FHIRFLARE support"><i class="fas fa-exclamation-circle me-1"></i> Raise an Issue</a>
|
<a href="https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit/issues/new/choose" class="text-danger text-decoration-none" aria-label="FHIRFLARE support"><i class="fas fa-exclamation-circle me-1"></i> Raise an Issue</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-right">
|
<div class="footer-right">
|
||||||
|
<a class="nav-link {{ 'active' if request.endpoint == 'flasgger.apidocs' else '' }}" href="{{ url_for('flasgger.apidocs') }}" aria-label="API Documentation"><i class="fas fa-book-open me-1"></i> API Docs</a>
|
||||||
<a href="https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit/discussions" target="_blank" rel="noreferrer" aria-label="Project Discussion">Project Discussions</a>
|
<a href="https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit/discussions" target="_blank" rel="noreferrer" aria-label="Project Discussion">Project Discussions</a>
|
||||||
<a href="https://github.com/Sudo-JHare" aria-label="Developer">Developer</a>
|
<a href="https://github.com/Sudo-JHare" aria-label="Developer">Developer</a>
|
||||||
<a href="https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit/blob/main/LICENSE.md" aria-label="License">License</a>
|
<a href="https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit/blob/main/LICENSE.md" aria-label="License">License</a>
|
||||||
@ -844,41 +865,38 @@ html[data-theme="dark"] .structure-tree-root .list-group-item-warning {
|
|||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
|
||||||
<script>
|
<script>
|
||||||
|
function getCookie(name) {
|
||||||
|
const value = `; ${document.cookie}`;
|
||||||
|
const parts = value.split(`; ${name}=`);
|
||||||
|
if (parts.length === 2) return parts.pop().split(';').shift();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function toggleTheme() {
|
function toggleTheme() {
|
||||||
const html = document.documentElement;
|
const html = document.documentElement;
|
||||||
const toggle = document.getElementById('themeToggle');
|
const toggle = document.getElementById('themeToggle');
|
||||||
const sunIcon = document.querySelector('.fa-sun');
|
const sunIcon = document.querySelector('.fa-sun');
|
||||||
const moonIcon = document.querySelector('.fa-moon');
|
const moonIcon = document.querySelector('.fa-moon');
|
||||||
if (toggle.checked) {
|
const newTheme = toggle.checked ? 'dark' : 'light';
|
||||||
html.setAttribute('data-theme', 'dark');
|
html.setAttribute('data-theme', newTheme);
|
||||||
localStorage.setItem('theme', 'dark');
|
localStorage.setItem('theme', newTheme);
|
||||||
sunIcon.classList.add('d-none');
|
sunIcon.classList.toggle('d-none', toggle.checked);
|
||||||
moonIcon.classList.remove('d-none');
|
moonIcon.classList.toggle('d-none', !toggle.checked);
|
||||||
} else {
|
document.cookie = `theme=${newTheme}; path=/; max-age=31536000`;
|
||||||
html.removeAttribute('data-theme');
|
|
||||||
localStorage.setItem('theme', 'light');
|
|
||||||
sunIcon.classList.remove('d-none');
|
|
||||||
moonIcon.classList.add('d-none');
|
|
||||||
}
|
|
||||||
if (typeof loadLottieAnimation === 'function') {
|
if (typeof loadLottieAnimation === 'function') {
|
||||||
const newTheme = toggle.checked ? 'dark' : 'light';
|
|
||||||
loadLottieAnimation(newTheme);
|
loadLottieAnimation(newTheme);
|
||||||
}
|
}
|
||||||
// Set cookie to persist theme
|
|
||||||
document.cookie = `theme=${toggle.checked ? 'dark' : 'light'}; path=/; max-age=31536000`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const savedTheme = localStorage.getItem('theme') || (document.cookie.includes('theme=dark') ? 'dark' : 'light');
|
const savedTheme = localStorage.getItem('theme') || getCookie('theme') || 'light';
|
||||||
const themeToggle = document.getElementById('themeToggle');
|
const themeToggle = document.getElementById('themeToggle');
|
||||||
const sunIcon = document.querySelector('.fa-sun');
|
const sunIcon = document.querySelector('.fa-sun');
|
||||||
const moonIcon = document.querySelector('.fa-moon');
|
const moonIcon = document.querySelector('.fa-moon');
|
||||||
if (savedTheme === 'dark' && themeToggle) {
|
document.documentElement.setAttribute('data-theme', savedTheme);
|
||||||
document.documentElement.setAttribute('data-theme', 'dark');
|
themeToggle.checked = savedTheme === 'dark';
|
||||||
themeToggle.checked = true;
|
sunIcon.classList.toggle('d-none', savedTheme === 'dark');
|
||||||
sunIcon.classList.add('d-none');
|
moonIcon.classList.toggle('d-none', savedTheme !== 'dark');
|
||||||
moonIcon.classList.remove('d-none');
|
|
||||||
}
|
|
||||||
const tooltips = document.querySelectorAll('[data-bs-toggle="tooltip"]');
|
const tooltips = document.querySelectorAll('[data-bs-toggle="tooltip"]');
|
||||||
tooltips.forEach(t => new bootstrap.Tooltip(t));
|
tooltips.forEach(t => new bootstrap.Tooltip(t));
|
||||||
});
|
});
|
||||||
@ -887,4 +905,4 @@ html[data-theme="dark"] .structure-tree-root .list-group-item-warning {
|
|||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
|
||||||
{% block scripts %}{% endblock %}
|
{% block scripts %}{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
234
templates/config_hapi.html
Normal file
234
templates/config_hapi.html
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="container mt-4">
|
||||||
|
<h1 class="mb-4">HAPI FHIR Configuration Manager</h1>
|
||||||
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
|
{% if messages %}
|
||||||
|
{% for category, message in messages %}
|
||||||
|
<div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
|
||||||
|
{{ message }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
<div id="loading" class="text-center my-4">
|
||||||
|
<p>Loading configuration...</p>
|
||||||
|
<div class="spinner-border" role="status">
|
||||||
|
<span class="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="error" class="alert alert-danger d-none" role="alert"></div>
|
||||||
|
<div id="config-form" class="d-none">
|
||||||
|
<div class="mb-3">
|
||||||
|
<button id="save-btn" class="btn btn-primary me-2">Save Configuration</button>
|
||||||
|
<button id="restart-btn" class="btn btn-success">Restart HAPI Server</button>
|
||||||
|
<span id="restart-status" class="ms-3 text-success d-none"></span>
|
||||||
|
</div>
|
||||||
|
<div id="config-sections"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Configuration descriptions from HAPI FHIR JPA documentation
|
||||||
|
const CONFIG_DESCRIPTIONS = {
|
||||||
|
'hapi.fhir.narrative_enabled': 'Enables or disables the generation of FHIR resource narratives (human-readable summaries). Set to false to reduce processing overhead. Default: false.',
|
||||||
|
'hapi.fhir.mdm_enabled': 'Enables Master Data Management (MDM) features for linking and matching resources (e.g., Patient records). Requires mdm_rules_json_location to be set. Default: false.',
|
||||||
|
'hapi.fhir.mdm_rules_json_location': 'Path to the JSON file defining MDM rules for resource matching. Example: "mdm-rules.json".',
|
||||||
|
'hapi.fhir.cors.allow_Credentials': 'Allows credentials (e.g., cookies, authorization headers) in CORS requests. Set to true to support authenticated cross-origin requests. Default: true.',
|
||||||
|
'hapi.fhir.cors.allowed_origin': 'List of allowed origins for CORS requests. Use ["*"] to allow all origins or specify domains (e.g., ["https://example.com"]). Default: ["*"].',
|
||||||
|
'hapi.fhir.tester.home.name': 'Name of the local tester configuration for HAPI FHIR testing UI. Example: "Local Tester".',
|
||||||
|
'hapi.fhir.tester.home.server_address': 'URL of the local FHIR server for testing. Example: "http://localhost:8080/fhir".',
|
||||||
|
'hapi.fhir.tester.home.refuse_to_fetch_third_party_urls': 'Prevents the tester from fetching third-party URLs. Set to true for security. Default: false.',
|
||||||
|
'hapi.fhir.tester.home.fhir_version': 'FHIR version for the local tester (e.g., "R4", "R5"). Default: R4.',
|
||||||
|
'hapi.fhir.tester.global.name': 'Name of the global tester configuration for HAPI FHIR testing UI. Example: "Global Tester".',
|
||||||
|
'hapi.fhir.tester.global.server_address': 'URL of the global FHIR server for testing. Example: "http://hapi.fhir.org/baseR4".',
|
||||||
|
'hapi.fhir.tester.global.refuse_to_fetch_third_party_urls': 'Prevents the global tester from fetching third-party URLs. Default: false.',
|
||||||
|
'hapi.fhir.tester.global.fhir_version': 'FHIR version for the global tester (e.g., "R4"). Default: R4.',
|
||||||
|
'hapi.fhir.cr.enabled': 'Enables Clinical Reasoning (CR) module for operations like evaluate-measure. Default: false.',
|
||||||
|
'hapi.fhir.cr.caregaps.reporter': 'Reporter mode for care gaps in the CR module. Default: "default".',
|
||||||
|
'hapi.fhir.cr.caregaps.section_author': 'Author identifier for care gap sections. Default: "default".',
|
||||||
|
'hapi.fhir.cr.cql.use_embedded_libraries': 'Uses embedded CQL libraries for Clinical Quality Language processing. Default: true.',
|
||||||
|
'hapi.fhir.inline_resource_storage_below_size': 'Maximum size (in bytes) for storing FHIR resources inline in the database. Default: 4000.',
|
||||||
|
'hapi.fhir.advanced_lucene_indexing': 'Enables experimental advanced Lucene/Elasticsearch indexing for enhanced search capabilities. Default: false. Note: May not support all FHIR features like _total=accurate.',
|
||||||
|
'hapi.fhir.enable_index_of_type': 'Enables indexing of resource types for faster searches. Default: true.',
|
||||||
|
// Add more descriptions as needed based on your application.yaml
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const configForm = document.getElementById('config-form');
|
||||||
|
const loading = document.getElementById('loading');
|
||||||
|
const errorDiv = document.getElementById('error');
|
||||||
|
const configSections = document.getElementById('config-sections');
|
||||||
|
const saveBtn = document.getElementById('save-btn');
|
||||||
|
const restartBtn = document.getElementById('restart-btn');
|
||||||
|
const restartStatus = document.getElementById('restart-status');
|
||||||
|
let configData = {};
|
||||||
|
|
||||||
|
// Fetch initial configuration
|
||||||
|
fetch('/api/config')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
configData = data;
|
||||||
|
renderConfigSections();
|
||||||
|
loading.classList.add('d-none');
|
||||||
|
configForm.classList.remove('d-none');
|
||||||
|
// Initialize Bootstrap tooltips
|
||||||
|
document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(el => {
|
||||||
|
new bootstrap.Tooltip(el);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
showError('Failed to load configuration');
|
||||||
|
loading.classList.add('d-none');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Render configuration sections
|
||||||
|
function renderConfigSections() {
|
||||||
|
configSections.innerHTML = '';
|
||||||
|
Object.entries(configData).forEach(([section, values]) => {
|
||||||
|
const sectionDiv = document.createElement('div');
|
||||||
|
sectionDiv.className = 'card mb-3';
|
||||||
|
sectionDiv.innerHTML = `
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title">${section.charAt(0).toUpperCase() + section.slice(1)}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
${renderInputs(values, [section])}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
configSections.appendChild(sectionDiv);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render inputs recursively with descriptions
|
||||||
|
function renderInputs(obj, path) {
|
||||||
|
let html = '';
|
||||||
|
Object.entries(obj).forEach(([key, value]) => {
|
||||||
|
const inputId = path.concat(key).join('-');
|
||||||
|
const configPath = path.concat(key).join('.');
|
||||||
|
const description = CONFIG_DESCRIPTIONS[configPath] || 'No description available.';
|
||||||
|
if (typeof value === 'boolean') {
|
||||||
|
html += `
|
||||||
|
<div class="form-check mb-2">
|
||||||
|
<input class="form-check-input" type="checkbox" id="${inputId}" ${value ? 'checked' : ''}>
|
||||||
|
<label class="form-check-label" for="${inputId}">
|
||||||
|
${key}
|
||||||
|
<i class="fas fa-info-circle ms-1" data-bs-toggle="tooltip" data-bs-placement="top" title="${description}"></i>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else if (typeof value === 'string' || typeof value === 'number') {
|
||||||
|
html += `
|
||||||
|
<div class="mb-2">
|
||||||
|
<label for="${inputId}" class="form-label">
|
||||||
|
${key}
|
||||||
|
<i class="fas fa-info-circle ms-1" data-bs-toggle="tooltip" data-bs-placement="top" title="${description}"></i>
|
||||||
|
</label>
|
||||||
|
<input type="text" class="form-control" id="${inputId}" value="${value}">
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else if (Array.isArray(value)) {
|
||||||
|
html += `
|
||||||
|
<div class="mb-2">
|
||||||
|
<label for="${inputId}" class="form-label">
|
||||||
|
${key} (comma-separated)
|
||||||
|
<i class="fas fa-info-circle ms-1" data-bs-toggle="tooltip" data-bs-placement="top" title="${description}"></i>
|
||||||
|
</label>
|
||||||
|
<input type="text" class="form-control" id="${inputId}" value="${value.join(',')}">
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else if (typeof value === 'object' && value !== null) {
|
||||||
|
html += `
|
||||||
|
<div class="border-start ps-3 ms-3">
|
||||||
|
<h4>${key}</h4>
|
||||||
|
${renderInputs(value, path.concat(key))}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update config data on input change
|
||||||
|
configSections.addEventListener('change', (e) => {
|
||||||
|
const path = e.target.id.split('-');
|
||||||
|
let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
|
||||||
|
if (e.target.type === 'text' && e.target.value.includes(',')) {
|
||||||
|
value = e.target.value.split(',').map(v => v.trim());
|
||||||
|
}
|
||||||
|
updateConfig(path, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update config object
|
||||||
|
function updateConfig(path, value) {
|
||||||
|
let current = configData;
|
||||||
|
for (let i = 0; i < path.length - 1; i++) {
|
||||||
|
current = current[path[i]];
|
||||||
|
}
|
||||||
|
current[path[path.length - 1]] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save configuration
|
||||||
|
saveBtn.addEventListener('click', () => {
|
||||||
|
fetch('/api/config', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(configData)
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.error) {
|
||||||
|
showError(data.error);
|
||||||
|
} else {
|
||||||
|
showSuccess('Configuration saved successfully');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => showError('Failed to save configuration'));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Restart HAPI server
|
||||||
|
restartBtn.addEventListener('click', () => {
|
||||||
|
restartStatus.textContent = 'Restarting...';
|
||||||
|
restartStatus.classList.remove('d-none');
|
||||||
|
fetch('/api/restart-tomcat', { method: 'POST' })
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.error) {
|
||||||
|
showError(data.error);
|
||||||
|
restartStatus.classList.add('d-none');
|
||||||
|
} else {
|
||||||
|
restartStatus.textContent = 'HAPI server restarted successfully';
|
||||||
|
setTimeout(() => {
|
||||||
|
restartStatus.classList.add('d-none');
|
||||||
|
restartStatus.textContent = '';
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
showError('Failed to restart HAPI server');
|
||||||
|
restartStatus.classList.add('d-none');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show error message
|
||||||
|
function showError(message) {
|
||||||
|
errorDiv.textContent = message;
|
||||||
|
errorDiv.classList.remove('d-none');
|
||||||
|
setTimeout(() => errorDiv.classList.add('d-none'), 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
function showSuccess(message) {
|
||||||
|
const alert = document.createElement('div');
|
||||||
|
alert.className = 'alert alert-success alert-dismissible fade show';
|
||||||
|
alert.innerHTML = `
|
||||||
|
${message}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
`;
|
||||||
|
document.querySelector('.container').insertBefore(alert, configForm);
|
||||||
|
setTimeout(() => alert.remove(), 5000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user