Version in self hosted (#770)

* Enhance Application Version Management in Docker and Feature Flags

- Added a new build argument `APP_VERSION` in the Docker configuration files to facilitate version tracking during builds.
- Introduced a private method `getAppVersion` in `FeatureFlagsController` to retrieve the application version from the Docker environment, enhancing the feature flags response with version information.
- Updated the `app.php` configuration file to include a new entry for `docker_version`, allowing for better version management and fallback when the Docker build version is unavailable.

These changes aim to improve the application's versioning capabilities, ensuring that the version is consistently available across different components and environments.

* Refactor OpenFormFooter and Update Feature Flags Handling

- Modified `OpenFormFooter.vue` to include a version display when available, enhancing user awareness of the application version.
- Refactored the script section to use the `<script setup>` syntax, improving readability and reactivity by utilizing `ref` and `computed` for reactive properties.
- Removed the deprecated `feature-flags.js` plugin and updated `featureFlags.js` to ensure proper SSR compatibility and error handling during feature flag fetching.

These changes aim to improve the user interface by providing version information and enhance the overall code structure for better maintainability and performance.
This commit is contained in:
Julien Nahum 2025-05-27 17:44:42 +02:00 committed by GitHub
parent a030a84652
commit f3a02df80e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 68 additions and 38 deletions

View File

@ -60,6 +60,7 @@ jobs:
build-args: | build-args: |
APP_ENV=${{ env.VERSION == 'dev' && 'local' || 'production' }} APP_ENV=${{ env.VERSION == 'dev' && 'local' || 'production' }}
COMPOSER_FLAGS=${{ env.VERSION == 'dev' && '--optimize-autoloader --no-interaction' || '--no-dev --optimize-autoloader --no-interaction' }} COMPOSER_FLAGS=${{ env.VERSION == 'dev' && '--optimize-autoloader --no-interaction' || '--no-dev --optimize-autoloader --no-interaction' }}
APP_VERSION=${{ env.VERSION }}
tags: ${{ env.API_TAGS }} tags: ${{ env.API_TAGS }}
cache-from: type=registry,ref=${{secrets.DOCKER_API_REPO}}:dev cache-from: type=registry,ref=${{secrets.DOCKER_API_REPO}}:dev
cache-to: type=inline cache-to: type=inline
@ -71,6 +72,8 @@ jobs:
file: docker/Dockerfile.client file: docker/Dockerfile.client
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
push: true push: true
build-args: |
APP_VERSION=${{ env.VERSION }}
tags: ${{ env.UI_TAGS }} tags: ${{ env.UI_TAGS }}
cache-from: type=registry,ref=${{secrets.DOCKER_UI_REPO}}:dev cache-from: type=registry,ref=${{secrets.DOCKER_UI_REPO}}:dev
cache-to: type=inline cache-to: type=inline

View File

@ -14,6 +14,7 @@ class FeatureFlagsController extends Controller
'self_hosted' => config('app.self_hosted', true), 'self_hosted' => config('app.self_hosted', true),
'custom_domains' => config('custom-domains.enabled', false), 'custom_domains' => config('custom-domains.enabled', false),
'ai_features' => !empty(config('services.openai.api_key')), 'ai_features' => !empty(config('services.openai.api_key')),
'version' => $this->getAppVersion(),
'billing' => [ 'billing' => [
'enabled' => !empty(config('cashier.key')) && !empty(config('cashier.secret')), 'enabled' => !empty(config('cashier.key')) && !empty(config('cashier.secret')),
@ -44,4 +45,17 @@ class FeatureFlagsController extends Controller
return response()->json($featureFlags); return response()->json($featureFlags);
} }
/**
* Get the application version from Docker environment or fallback
*/
private function getAppVersion(): ?string
{
// Only return version for self-hosted installations
if (!config('app.self_hosted', true)) {
return null;
}
return config('app.docker_version');
}
} }

View File

@ -15,6 +15,17 @@ return [
'name' => env('APP_NAME', 'OpnForm'), 'name' => env('APP_NAME', 'OpnForm'),
/*
|--------------------------------------------------------------------------
| Application Version
|--------------------------------------------------------------------------
|
| This value is the version of your application. Used for display purposes
| and fallback when Docker build version is not available.
|
*/
'docker_version' => env('APP_VERSION_DOCKER'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Application Environment | Application Environment

View File

@ -4,6 +4,9 @@
<div class="flex mt-2 items-center"> <div class="flex mt-2 items-center">
<p class="text-sm text-gray-600 dark:text-gray-400 text-center w-full"> <p class="text-sm text-gray-600 dark:text-gray-400 text-center w-full">
© Copyright {{ currYear }}. All Rights Reserved © Copyright {{ currYear }}. All Rights Reserved
<span v-if="version">
<br>Version {{ version }}
</span>
</p> </p>
</div> </div>
<div class="flex justify-center mt-5 md:mt-0"> <div class="flex justify-center mt-5 md:mt-0">
@ -82,22 +85,14 @@
</div> </div>
</template> </template>
<script> <script setup>
import { computed } from "vue"
import opnformConfig from "~/opnform.config.js" import opnformConfig from "~/opnform.config.js"
export default { const authStore = useAuthStore()
setup() {
const authStore = useAuthStore()
return {
user: computed(() => authStore.user),
appStore: useAppStore(),
opnformConfig,
}
},
data: () => ({ const user = computed(() => authStore.user)
currYear: new Date().getFullYear(), const currYear = ref(new Date().getFullYear())
}),
} // Use the reactive version for proper template reactivity
const version = computed(() => useFeatureFlag('version'))
</script> </script>

View File

@ -1,11 +1,5 @@
export default defineNuxtRouteMiddleware(async () => { export default defineNuxtRouteMiddleware(async () => {
const authStore = useAuthStore() const authStore = useAuthStore()
const featureFlagsStore = useFeatureFlagsStore()
// Ensure feature flags are loaded
if (!featureFlagsStore.isLoaded) {
await featureFlagsStore.fetchFlags()
}
if (useFeatureFlag('self_hosted')) { if (useFeatureFlag('self_hosted')) {
if (authStore.check && authStore.user?.email === 'admin@opnform.com') { if (authStore.check && authStore.user?.email === 'admin@opnform.com') {

View File

@ -1,10 +0,0 @@
import { useFeatureFlagsStore } from '~/stores/featureFlags'
export default defineNuxtPlugin(async () => {
const featureFlagsStore = useFeatureFlagsStore()
// Load flags if they haven't been loaded yet
if (!featureFlagsStore.isLoaded) {
await featureFlagsStore.fetchFlags()
}
})

View File

@ -1,9 +1,18 @@
import { useFeatureFlagsStore } from '~/stores/featureFlags' import { useFeatureFlagsStore } from '~/stores/featureFlags'
export default defineNuxtPlugin((nuxtApp) => { export default defineNuxtPlugin(async (nuxtApp) => {
const featureFlagsStore = useFeatureFlagsStore() // Get the pinia instance for SSR compatibility
const { $pinia } = nuxtApp
nuxtApp.provide('featureFlag', (key, defaultValue = false) => { try {
return featureFlagsStore.getFlag(key, defaultValue) // Pass pinia instance for SSR compatibility
}) const featureFlagsStore = useFeatureFlagsStore($pinia)
// Fetch flags during SSR to prevent hydration mismatches
if (!featureFlagsStore.isLoaded) {
await featureFlagsStore.fetchFlags()
}
} catch (error) {
console.error('Feature flags plugin failed:', error)
}
}) })

View File

@ -1,7 +1,11 @@
--- ---
services: services:
api: &api-environment api: &api-environment
image: jhumanj/opnform-api:latest build:
context: .
dockerfile: docker/Dockerfile.api
args:
APP_VERSION: "local-test-1.0.0"
container_name: opnform-api container_name: opnform-api
volumes: &api-environment-volumes volumes: &api-environment-volumes
- opnform_storage:/usr/share/nginx/html/storage:rw - opnform_storage:/usr/share/nginx/html/storage:rw
@ -62,7 +66,11 @@ services:
start_period: 70s # Allow time for first scheduled run and cache write start_period: 70s # Allow time for first scheduled run and cache write
ui: ui:
image: jhumanj/opnform-client:latest build:
context: .
dockerfile: docker/Dockerfile.client
args:
APP_VERSION: "local-test-1.0.0"
container_name: opnform-client container_name: opnform-client
env_file: env_file:
- ./client/.env - ./client/.env

View File

@ -27,6 +27,9 @@ RUN composer install --optimize-autoloader --no-interaction \
# Final stage - smaller runtime image # Final stage - smaller runtime image
FROM php:8.3-fpm-alpine FROM php:8.3-fpm-alpine
# Accept version build argument
ARG APP_VERSION=unknown
# Install runtime dependencies # Install runtime dependencies
RUN apk add --no-cache \ RUN apk add --no-cache \
libzip \ libzip \
@ -75,6 +78,9 @@ RUN mkdir -p storage/framework/sessions \
# Copy the entire application from the builder stage # Copy the entire application from the builder stage
COPY --from=builder /app/ ./ COPY --from=builder /app/ ./
# Set version as environment variable (more reliable than file approach)
ENV APP_VERSION_DOCKER=$APP_VERSION
# Setup entrypoint # Setup entrypoint
COPY docker/php-fpm-entrypoint /usr/local/bin/opnform-entrypoint COPY docker/php-fpm-entrypoint /usr/local/bin/opnform-entrypoint
RUN chmod a+x /usr/local/bin/* RUN chmod a+x /usr/local/bin/*