Merge pull request #456 from databasus/develop

Develop
This commit is contained in:
Rostislav Dugin
2026-03-21 14:15:21 +03:00
committed by GitHub
12 changed files with 151 additions and 160 deletions

View File

@@ -257,6 +257,9 @@ COPY backend/migrations ./migrations
# Copy UI files
COPY --from=backend-build /app/ui/build ./ui/build
# Copy cloud static HTML template (injected into index.html at startup when IS_CLOUD=true)
COPY frontend/cloud-root-content.html /app/cloud-root-content.html
# Copy agent binaries (both architectures) — served by the backend
# at GET /api/v1/system/agent?arch=amd64|arm64
COPY --from=agent-build /agent-binaries ./agent-binaries
@@ -326,6 +329,23 @@ if [ -n "\${ANALYTICS_SCRIPT:-}" ]; then
fi
fi
# Inject static HTML into root div for cloud mode (payment system requires visible legal links)
if [ "\${IS_CLOUD:-false}" = "true" ]; then
if ! grep -q "cloud-static-content" /app/ui/build/index.html 2>/dev/null; then
echo "Injecting cloud static HTML content..."
perl -i -pe '
BEGIN {
open my \$fh, "<", "/app/cloud-root-content.html" or die;
local \$/;
\$c = <\$fh>;
close \$fh;
\$c =~ s/\\n/ /g;
}
s/<div id="root"><\\/div>/<div id="root"><!-- cloud-static-content -->\$c<\\/div>/
' /app/ui/build/index.html
fi
fi
# Ensure proper ownership of data directory
echo "Setting up data directory permissions..."
mkdir -p /databasus-data/pgdata
@@ -439,7 +459,7 @@ fi
echo "Setting up database and user..."
gosu postgres \$PG_BIN/psql -p 5437 -h localhost -d postgres << 'SQL'
# We use stub password, because internal DB is not exposed outside container
-- We use stub password, because internal DB is not exposed outside container
ALTER USER postgres WITH PASSWORD 'Q1234567';
SELECT 'CREATE DATABASE databasus OWNER postgres'
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'databasus')

View File

@@ -109,6 +109,7 @@ func (uc *CreateMariadbBackupUsecase) buildMariadbDumpArgs(
"--routines",
"--quick",
"--skip-extended-insert",
"--skip-add-locks",
"--verbose",
}
@@ -129,6 +130,8 @@ func (uc *CreateMariadbBackupUsecase) buildMariadbDumpArgs(
if mdb.IsHttps {
args = append(args, "--ssl")
args = append(args, "--skip-ssl-verify-server-cert")
} else {
args = append(args, "--skip-ssl")
}
if mdb.Database != nil && *mdb.Database != "" {

View File

@@ -108,6 +108,7 @@ func (uc *CreateMysqlBackupUsecase) buildMysqldumpArgs(my *mysqltypes.MysqlDatab
"--set-gtid-purged=OFF",
"--quick",
"--skip-extended-insert",
"--skip-add-locks",
"--verbose",
}
@@ -126,6 +127,8 @@ func (uc *CreateMysqlBackupUsecase) buildMysqldumpArgs(my *mysqltypes.MysqlDatab
if my.IsHttps {
args = append(args, "--ssl-mode=REQUIRED")
} else {
args = append(args, "--ssl-mode=DISABLED")
}
if my.Database != nil && *my.Database != "" {
@@ -326,6 +329,8 @@ port=%d
if myConfig.IsHttps {
content += "ssl-mode=REQUIRED\n"
} else {
content += "ssl-mode=DISABLED\n"
}
err = os.WriteFile(myCnfFile, []byte(content), 0o600)

View File

@@ -77,6 +77,8 @@ func (uc *RestoreMariadbBackupUsecase) Execute(
if mdb.IsHttps {
args = append(args, "--ssl")
args = append(args, "--skip-ssl-verify-server-cert")
} else {
args = append(args, "--skip-ssl")
}
if mdb.Database != nil && *mdb.Database != "" {

View File

@@ -76,6 +76,8 @@ func (uc *RestoreMysqlBackupUsecase) Execute(
if my.IsHttps {
args = append(args, "--ssl-mode=REQUIRED")
} else {
args = append(args, "--ssl-mode=DISABLED")
}
if my.Database != nil && *my.Database != "" {
@@ -305,6 +307,8 @@ port=%d
if myConfig.IsHttps {
content += "ssl-mode=REQUIRED\n"
} else {
content += "ssl-mode=DISABLED\n"
}
err = os.WriteFile(myCnfFile, []byte(content), 0o600)

View File

@@ -0,0 +1,40 @@
<div style="font-family: 'Jost', system-ui, -apple-system, sans-serif; display: flex; flex-direction: column; min-height: 100vh; min-height: 100dvh; background: #fff;">
<!-- Navbar -->
<div style="display: flex; align-items: center; padding: 20px 20px 0; height: 65px;">
<a href="https://databasus.com" target="_blank" rel="noreferrer" style="display: flex; align-items: center; gap: 12px; text-decoration: none;">
<img src="/logo.svg" style="height: 45px; width: 45px; padding: 4px;" alt="Databasus" />
<span style="font-size: 1.25rem; font-weight: 700; color: #2563eb;">Databasus</span>
</a>
</div>
<!-- Sign-in form stub -->
<div style="display: flex; justify-content: center; margin-top: 10vh;">
<div style="width: 100%; max-width: 300px; padding: 0 16px; box-sizing: border-box;">
<div style="text-align: center; font-size: 1.5rem; font-weight: 700; margin-bottom: 24px; color: #111;">Sign in</div>
<div style="margin-bottom: 4px; font-size: 14px; font-weight: 600; color: #111;">Your email</div>
<input type="email" placeholder="your@email.com" disabled
style="width: 100%; padding: 8px 12px; border: 1px solid #d1d5db; border-radius: 6px; box-sizing: border-box; font-size: 14px; font-family: inherit; background: #fff;" />
<div style="margin: 12px 0 4px; font-size: 14px; font-weight: 600; color: #111;">Password</div>
<input type="password" placeholder="********" disabled
style="width: 100%; padding: 8px 12px; border: 1px solid #d1d5db; border-radius: 6px; box-sizing: border-box; font-size: 14px; font-family: inherit; background: #fff;" />
<button disabled
style="width: 100%; margin-top: 16px; padding: 8px 16px; background: #1677ff; color: #fff; border: none; border-radius: 6px; font-size: 14px; font-family: inherit; cursor: default;">
Sign in
</button>
</div>
</div>
<footer style="margin-top: auto; padding-bottom: 20px; text-align: center;">
<div style="font-size: 14px; color: #4b5563;">
<a href="https://databasus.com/terms-of-use-cloud" style="color: #2563eb; text-decoration: underline;">Terms of Use</a>
&nbsp;|&nbsp;
<a href="https://databasus.com/privacy-cloud" style="color: #2563eb; text-decoration: underline;">Privacy Policy</a>
</div>
<div style="margin-top: 20px; font-size: 14px; color: #6b7280;">
info@databasus.com | &copy; 2026 Databasus. All rights reserved.
</div>
</footer>
</div>

View File

@@ -1 +0,0 @@
export { PlaygroundWarningComponent } from './ui/PlaygroundWarningComponent';

View File

@@ -1,149 +0,0 @@
import { Modal } from 'antd';
import type { JSX } from 'react';
import { useEffect, useState } from 'react';
import { IS_CLOUD } from '../../../constants';
const STORAGE_KEY = 'databasus_playground_info_dismissed';
const TIMEOUT_SECONDS = 30;
export const PlaygroundWarningComponent = (): JSX.Element => {
const [isVisible, setIsVisible] = useState(false);
const [remainingSeconds, setRemainingSeconds] = useState(TIMEOUT_SECONDS);
const [isButtonEnabled, setIsButtonEnabled] = useState(false);
const handleClose = () => {
try {
localStorage.setItem(STORAGE_KEY, 'true');
} catch (e) {
console.warn('Failed to save playground modal state to localStorage:', e);
}
setIsVisible(false);
};
useEffect(() => {
if (!IS_CLOUD) {
return;
}
try {
const isDismissed = localStorage.getItem(STORAGE_KEY) === 'true';
if (!isDismissed) {
setIsVisible(true);
}
} catch (e) {
console.warn('Failed to read playground modal state from localStorage:', e);
setIsVisible(true);
}
}, []);
useEffect(() => {
if (!isVisible) {
return;
}
const interval = setInterval(() => {
setRemainingSeconds((prev) => {
if (prev <= 1) {
setIsButtonEnabled(true);
clearInterval(interval);
return 0;
}
return prev - 1;
});
}, 1000);
return () => clearInterval(interval);
}, [isVisible]);
return (
<Modal
title="Welcome to Databasus playground"
open={isVisible}
onOk={handleClose}
okText={
<div className="min-w-[100px]">
{isButtonEnabled ? 'Understood' : `${remainingSeconds}`}
</div>
}
okButtonProps={{ disabled: !isButtonEnabled }}
closable={false}
cancelButtonProps={{ style: { display: 'none' } }}
width={500}
centered
maskClosable={false}
>
<div className="space-y-6 py-4">
<div>
<h3 className="mb-2 text-lg font-semibold">What is Playground?</h3>
<p className="text-gray-700 dark:text-gray-300">
Playground is a dev environment of Databasus development team. It is used by Databasus
dev team to test new features and see issues which hard to detect when using self hosted
(without logs or reports).{' '}
<b>Here you can make backups for small and not critical databases for free</b>
</p>
</div>
<div>
<h3 className="mb-2 text-lg font-semibold">What is limit?</h3>
<ul className="list-disc space-y-1 pl-5 text-gray-700 dark:text-gray-300">
<li>Single backup size - 100 MB (~1.5 GB database)</li>
<li>Store period - 7 days</li>
</ul>
</div>
<div>
<h3 className="mb-2 text-lg font-semibold">Is it secure?</h3>
<p className="text-gray-700 dark:text-gray-300">
Yes, it&apos;s regular Databasus installation, secured and maintained by Databasus team.
More about security{' '}
<a
href="https://databasus.com/security"
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:underline dark:text-blue-400"
>
you can read here
</a>
</p>
</div>
<div>
<h3 className="mb-2 text-lg font-semibold">Can my data be currepted?</h3>
<p className="text-gray-700 dark:text-gray-300">
No, because playground use only read-only users and cannot affect your DB. Only issue
you can face is instability: playground background workers frequently reloaded so backup
can be slower or be restarted due to app restart. Do not rely production DBs on
playground, please. At once we may clean backups or something like this. At least, check
your backups here once a week
</p>
</div>
<div>
<h3 className="mb-2 text-lg font-semibold">What if I see an issue?</h3>
<p className="text-gray-700 dark:text-gray-300">
Create{' '}
<a
href="https://github.com/databasus/databasus/issues"
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:underline dark:text-blue-400"
>
GitHub issue
</a>{' '}
or write{' '}
<a
href="https://t.me/databasus_community"
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:underline dark:text-blue-400"
>
to the community
</a>
</p>
</div>
</div>
</Modal>
);
};

View File

@@ -166,6 +166,8 @@ export function SignInComponent({
</button>
)}
</div>
<div className="mb-10" />
</div>
);
}

View File

@@ -1,10 +1,10 @@
import { EyeInvisibleOutlined, EyeTwoTone } from '@ant-design/icons';
import { App, Button, Input } from 'antd';
import { App, Button, Checkbox, Input } from 'antd';
import { type JSX, useState } from 'react';
import { useCloudflareTurnstile } from '../../../shared/hooks/useCloudflareTurnstile';
import { GITHUB_CLIENT_ID, GOOGLE_CLIENT_ID } from '../../../constants';
import { GITHUB_CLIENT_ID, GOOGLE_CLIENT_ID, IS_CLOUD } from '../../../constants';
import { userApi } from '../../../entity/users';
import { StringUtils } from '../../../shared/lib';
import { FormValidator } from '../../../shared/lib/FormValidator';
@@ -34,6 +34,9 @@ export function SignUpComponent({ onSwitchToSignIn }: SignUpComponentProps): JSX
const [signUpError, setSignUpError] = useState('');
const [isTermsAccepted, setTermsAccepted] = useState(false);
const [isPolicyAccepted, setPolicyAccepted] = useState(false);
const { token, containerRef, resetCloudflareTurnstile } = useCloudflareTurnstile();
const validateFieldsForSignUp = (): boolean => {
@@ -179,10 +182,49 @@ export function SignUpComponent({ onSwitchToSignIn }: SignUpComponentProps): JSX
<div className="mt-3" />
{IS_CLOUD && (
<div className="mb-3 space-y-1 text-xs text-gray-600 dark:text-gray-400">
<div>
<Checkbox
checked={isTermsAccepted}
onChange={(e) => setTermsAccepted(e.target.checked)}
>
I agree to{' '}
<a
href="https://databasus.com/terms-of-use-cloud"
target="_blank"
rel="noreferrer"
className="underline"
style={{ color: 'inherit', textDecoration: 'underline' }}
>
Terms of Use
</a>
</Checkbox>
</div>
<div>
<Checkbox
checked={isPolicyAccepted}
onChange={(e) => setPolicyAccepted(e.target.checked)}
>
I agree to{' '}
<a
href="https://databasus.com/privacy-cloud"
target="_blank"
rel="noreferrer"
className="underline"
style={{ color: 'inherit', textDecoration: 'underline' }}
>
Privacy Policy
</a>
</Checkbox>
</div>
</div>
)}
<CloudflareTurnstileWidget containerRef={containerRef} />
<Button
disabled={isLoading}
disabled={isLoading || (IS_CLOUD && (!isTermsAccepted || !isPolicyAccepted))}
loading={isLoading}
className="w-full"
onClick={() => {
@@ -211,6 +253,8 @@ export function SignUpComponent({ onSwitchToSignIn }: SignUpComponentProps): JSX
</button>
</div>
)}
<div className="mb-10" />
</div>
);
}

View File

@@ -2,8 +2,8 @@ import { LoadingOutlined } from '@ant-design/icons';
import { Spin } from 'antd';
import { useEffect, useState } from 'react';
import { IS_CLOUD } from '../constants';
import { userApi } from '../entity/users';
import { PlaygroundWarningComponent } from '../features/playground';
import {
AdminPasswordComponent,
AuthNavbarComponent,
@@ -42,7 +42,7 @@ export function AuthPageComponent() {
}, []);
return (
<div className="h-full dark:bg-gray-900" style={{ height: screenHeight }}>
<div className="flex min-h-full flex-col dark:bg-gray-900" style={{ minHeight: screenHeight }}>
{isLoading ? (
<div className="flex h-screen w-screen items-center justify-center">
<Spin indicator={<LoadingOutlined spin />} size="large" />
@@ -84,7 +84,31 @@ export function AuthPageComponent() {
</div>
)}
<PlaygroundWarningComponent />
{IS_CLOUD && (
<footer className="mx-10 mt-auto pb-5 text-center text-sm text-gray-500 dark:text-gray-500">
<a
href="https://databasus.com/terms-of-use-cloud"
target="_blank"
rel="noreferrer"
className="underline"
style={{ color: 'inherit' }}
>
Terms of Use
</a>
{' | '}
<a
href="https://databasus.com/privacy-cloud"
target="_blank"
rel="noreferrer"
className="underline"
style={{ color: 'inherit' }}
>
Privacy Policy
</a>
{' | '}
info@databasus.com | &copy; 2026 Databasus. All rights reserved.
</footer>
)}
</div>
);
}

View File

@@ -14,7 +14,6 @@ import {
import { type WorkspaceResponse, workspaceApi } from '../../entity/workspaces';
import { DatabasesComponent } from '../../features/databases/ui/DatabasesComponent';
import { NotifiersComponent } from '../../features/notifiers/ui/NotifiersComponent';
import { PlaygroundWarningComponent } from '../../features/playground';
import { SettingsComponent } from '../../features/settings';
import { StoragesComponent } from '../../features/storages/ui/StoragesComponent';
import { ProfileComponent } from '../../features/users';
@@ -381,8 +380,6 @@ export const MainScreenComponent = () => {
workspacesCount={workspaces.length}
/>
)}
<PlaygroundWarningComponent />
</div>
);
};