V 1.2.1 Multiplatfoprm Build

Version auch auf der Login-Seite
This commit is contained in:
rxf
2026-03-05 09:05:27 +01:00
parent 74e5f76ec2
commit 46678cb644
6 changed files with 182 additions and 70 deletions

View File

@@ -37,7 +37,7 @@ RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs RUN adduser --system --uid 1001 nextjs
# Copy necessary files # Copy necessary files
#COPY --from=builder /app/public ./public # COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

View File

@@ -2,26 +2,26 @@
import { useActionState } from 'react'; import { useActionState } from 'react';
import { login } from './actions'; import { login } from './actions';
import packageJson from '@/package.json';
export default function LoginPage() { export default function LoginPage() {
const [state, loginAction, isPending] = useActionState(login, undefined); const [state, loginAction, isPending] = useActionState(login, undefined);
const version = packageJson.version;
const buildDate = process.env.NEXT_PUBLIC_BUILD_DATE || new Date().toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' });
return ( return (
<div className="min-h-screen bg-white py-4 px-4"> <div className="min-h-screen bg-white py-4 px-4">
<main className="max-w-7xl mx-auto border-2 border-black rounded-lg p-6 bg-[#FFFFDD]"> <main className="max-w-6xl mx-auto border-2 border-black rounded-lg p-6 bg-[#FFFFDD]">
<h1 className="text-3xl font-bold mb-6">Ausgaben - Log</h1> <div className="flex justify-between items-center mb-6">
<h1 className="text-3xl font-bold">Ausgaben - Log</h1>
<div className="flex items-center justify-center py-8">
<div className="max-w-md w-full space-y-8 bg-white p-8 rounded-2xl shadow-xl">
<div className="text-center">
<h2 className="text-2xl font-bold mb-2">Anmeldung</h2>
<p className="text-gray-600">
Bitte melden Sie sich an, um fortzufahren
</p>
</div> </div>
<form action={loginAction} className="mt-8 space-y-6"> <div className="flex justify-center py-10">
<div className="space-y-4"> <div className="w-full max-w-sm bg-white border border-gray-300 rounded-xl shadow-md p-8">
<h2 className="text-xl font-semibold text-gray-900 mb-6 text-center">Anmeldung</h2>
<form action={loginAction} className="space-y-5">
<div> <div>
<label <label
htmlFor="username" htmlFor="username"
@@ -35,7 +35,7 @@ export default function LoginPage() {
type="text" type="text"
required required
autoComplete="off" autoComplete="off"
className="appearance-none relative block w-full px-4 py-3 border border-gray-300 placeholder-gray-500 text-gray-900 bg-white rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors" className="w-full px-3 py-2 border-2 border-gray-400 rounded-lg bg-white text-gray-900 focus:border-blue-500 focus:outline-none text-sm"
placeholder="Benutzername" placeholder="Benutzername"
disabled={isPending} disabled={isPending}
/> />
@@ -54,15 +54,14 @@ export default function LoginPage() {
type="password" type="password"
required required
autoComplete="new-password" autoComplete="new-password"
className="appearance-none relative block w-full px-4 py-3 border border-gray-300 placeholder-gray-500 text-gray-900 bg-white rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors" className="w-full px-3 py-2 border-2 border-gray-400 rounded-lg bg-white text-gray-900 focus:border-blue-500 focus:outline-none text-sm"
placeholder="Passwort" placeholder="Passwort"
disabled={isPending} disabled={isPending}
/> />
</div> </div>
</div>
{state?.error && ( {state?.error && (
<div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-lg text-sm"> <div className="bg-red-50 border border-red-300 text-red-700 px-3 py-2 rounded-lg text-sm">
{state.error} {state.error}
</div> </div>
)} )}
@@ -70,14 +69,27 @@ export default function LoginPage() {
<button <button
type="submit" type="submit"
disabled={isPending} disabled={isPending}
className="w-full flex justify-center py-3 px-4 border border-transparent text-sm font-semibold rounded-lg text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200 shadow-lg hover:shadow-xl" className="w-full py-2 px-4 bg-[#85B7D7] hover:bg-[#6a9fc5] text-black font-medium rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed text-sm"
> >
{isPending ? 'Anmeldung läuft...' : 'Anmelden'} {isPending ? 'Anmeldung läuft...' : 'Anmelden'}
</button> </button>
</form> </form>
</div> </div>
</div> </div>
{/* Footer */}
<footer className="mt-8 flex justify-between items-center text-sm text-gray-600 px-4 ">
<div>
<a href="mailto:rxf@gmx.de" className="text-blue-600 hover:underline">
mailto:rxf@gmx.de
</a>
</div>
<div className="text-right">
Version {version} - {buildDate}
</div>
</footer>
</main> </main>
</div> </div>
); );
} }

83
app/login/page.tsx_xx Normal file
View File

@@ -0,0 +1,83 @@
'use client';
import { useActionState } from 'react';
import { login } from './actions';
export default function LoginPage() {
const [state, loginAction, isPending] = useActionState(login, undefined);
return (
<div className="min-h-screen bg-white py-4 px-4">
<main className="max-w-7xl mx-auto border-2 border-black rounded-lg p-6 bg-[#FFFFDD]">
<h1 className="text-3xl font-bold mb-6">Ausgaben - Log</h1>
<div className="flex items-center justify-center py-8">
<div className="max-w-md w-full space-y-8 bg-white p-8 rounded-2xl shadow-xl">
<div className="text-center">
<h2 className="text-2xl font-bold mb-2">Anmeldung</h2>
<p className="text-gray-600">
Bitte melden Sie sich an, um fortzufahren
</p>
</div>
<form action={loginAction} className="mt-8 space-y-6">
<div className="space-y-4">
<div>
<label
htmlFor="username"
className="block text-sm font-medium text-gray-700 mb-1"
>
Benutzername
</label>
<input
id="username"
name="username"
type="text"
required
autoComplete="off"
className="appearance-none relative block w-full px-4 py-3 border border-gray-300 placeholder-gray-500 text-gray-900 bg-white rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors"
placeholder="Benutzername"
disabled={isPending}
/>
</div>
<div>
<label
htmlFor="password"
className="block text-sm font-medium text-gray-700 mb-1"
>
Passwort
</label>
<input
id="password"
name="password"
type="password"
required
autoComplete="new-password"
className="appearance-none relative block w-full px-4 py-3 border border-gray-300 placeholder-gray-500 text-gray-900 bg-white rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors"
placeholder="Passwort"
disabled={isPending}
/>
</div>
</div>
{state?.error && (
<div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-lg text-sm">
{state.error}
</div>
)}
<button
type="submit"
disabled={isPending}
className="w-full flex justify-center py-3 px-4 border border-transparent text-sm font-semibold rounded-lg text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200 shadow-lg hover:shadow-xl"
>
{isPending ? 'Anmeldung läuft...' : 'Anmelden'}
</button>
</form>
</div>
</div>
</main>
</div>
);
}

View File

@@ -15,7 +15,7 @@ FULL_IMAGE="${REGISTRY}/${IMAGE_NAME}:${TAG}"
BUILD_DATE=$(date +%d.%m.%Y) BUILD_DATE=$(date +%d.%m.%Y)
echo "==========================================" echo "=========================================="
echo "Ausgaben-Next Deploy Script" echo "ausgaben-next Deploy Script"
echo "==========================================" echo "=========================================="
echo "Registry: ${REGISTRY}" echo "Registry: ${REGISTRY}"
echo "Image: ${IMAGE_NAME}" echo "Image: ${IMAGE_NAME}"
@@ -24,25 +24,29 @@ echo "Build-Datum: ${BUILD_DATE}"
echo "==========================================" echo "=========================================="
echo "" echo ""
# 1. Docker Image bauen # 1. Login zur Registry (falls noch nicht eingeloggt)
echo ">>> Baue Docker Image..."
docker build \
--build-arg BUILD_DATE="${BUILD_DATE}" \
-t "${IMAGE_NAME}:${TAG}" \
-t "${FULL_IMAGE}" \
.
echo ">>> Build erfolgreich!"
echo ""
# 2. Login zur Registry (falls noch nicht eingeloggt)
echo ">>> Login zu ${REGISTRY}..." echo ">>> Login zu ${REGISTRY}..."
docker login "${REGISTRY}" docker login "${REGISTRY}"
echo "" echo ""
# 3. Image pushen # 2. Multiplatform Builder einrichten (docker-container driver erforderlich)
echo ">>> Pushe Image zu ${REGISTRY}..." echo ">>> Richte Multiplatform Builder ein..."
docker push "${FULL_IMAGE}" if ! docker buildx inspect multiplatform-builder &>/dev/null; then
docker buildx create --name multiplatform-builder --driver docker-container --bootstrap
fi
docker buildx use multiplatform-builder
echo ""
# 3. Docker Image bauen und pushen (Multiplatform)
echo ">>> Baue Multiplatform Docker Image und pushe zu Registry..."
docker buildx build \
--platform linux/amd64,linux/arm64 \
--build-arg BUILD_DATE="${BUILD_DATE}" \
-t "${FULL_IMAGE}" \
--push \
.
echo ">>> Build und Push erfolgreich!"
echo "" echo ""
echo "==========================================" echo "=========================================="

17
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "ausgaben_next", "name": "ausgaben_next",
"version": "1.0.1", "version": "1.2.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ausgaben_next", "name": "ausgaben_next",
"version": "1.0.1", "version": "1.2.0",
"dependencies": { "dependencies": {
"bcryptjs": "^3.0.3", "bcryptjs": "^3.0.3",
"jose": "^6.1.3", "jose": "^6.1.3",
@@ -71,6 +71,7 @@
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.29.0", "@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0", "@babel/generator": "^7.29.0",
@@ -1561,6 +1562,7 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.34.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.34.tgz",
"integrity": "sha512-by3/Z0Qp+L9cAySEsSNNwZ6WWw8ywgGLPQGgbQDhNRSitqYgkgp4pErd23ZSCavbtUA2CN4jQtoB3T8nk4j3Rg==", "integrity": "sha512-by3/Z0Qp+L9cAySEsSNNwZ6WWw8ywgGLPQGgbQDhNRSitqYgkgp4pErd23ZSCavbtUA2CN4jQtoB3T8nk4j3Rg==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"undici-types": "~6.21.0" "undici-types": "~6.21.0"
} }
@@ -1571,6 +1573,7 @@
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"csstype": "^3.2.2" "csstype": "^3.2.2"
} }
@@ -1630,6 +1633,7 @@
"integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/scope-manager": "8.56.1",
"@typescript-eslint/types": "8.56.1", "@typescript-eslint/types": "8.56.1",
@@ -2155,6 +2159,7 @@
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@@ -2516,6 +2521,7 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"baseline-browser-mapping": "^2.9.0", "baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759", "caniuse-lite": "^1.0.30001759",
@@ -3092,6 +3098,7 @@
"integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1", "@eslint-community/regexpp": "^4.12.1",
@@ -3277,6 +3284,7 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@rtsao/scc": "^1.1.0", "@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9", "array-includes": "^3.1.9",
@@ -5569,6 +5577,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@@ -5578,6 +5587,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"scheduler": "^0.27.0" "scheduler": "^0.27.0"
}, },
@@ -6287,6 +6297,7 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@@ -6449,6 +6460,7 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
@@ -6723,6 +6735,7 @@
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"funding": { "funding": {
"url": "https://github.com/sponsors/colinhacks" "url": "https://github.com/sponsors/colinhacks"
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "ausgaben_next", "name": "ausgaben_next",
"version": "1.2.0", "version": "1.2.1",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev -p 3005", "dev": "next dev -p 3005",