mirror of
https://github.com/databasus/databasus.git
synced 2026-04-06 00:32:03 +02:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0721a43e1 | ||
|
|
461e15cd7a | ||
|
|
69a53936f5 | ||
|
|
2bafec3c19 | ||
|
|
422b44dfdc |
102
.github/CODE_OF_CONDUCT.md
vendored
Normal file
102
.github/CODE_OF_CONDUCT.md
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
# Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors and maintainers pledge to make participation in the Postgresus community a friendly and welcoming experience for everyone, regardless of background, experience level or personal circumstances.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
### Examples of behavior that contributes to a positive environment
|
||||
|
||||
- Using welcoming and inclusive language
|
||||
- Being respectful of differing viewpoints and experiences
|
||||
- Gracefully accepting constructive criticism
|
||||
- Focusing on what is best for the community
|
||||
- Showing empathy towards other community members
|
||||
- Helping newcomers get started with contributions
|
||||
- Providing clear and constructive feedback on pull requests
|
||||
- Celebrating successes and acknowledging contributions
|
||||
|
||||
### Examples of unacceptable behavior
|
||||
|
||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
- Publishing others' private information, such as physical or email addresses, without their explicit permission
|
||||
- Spam, self-promotion or off-topic content in project spaces
|
||||
- Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, including:
|
||||
|
||||
- GitHub repositories (issues, pull requests, discussions, comments)
|
||||
- Telegram channels and direct messages related to Postgresus
|
||||
- Social media interactions when representing the project
|
||||
- Community forums and online discussions
|
||||
- Any other spaces where Postgresus community members interact
|
||||
|
||||
This Code of Conduct also applies when an individual is officially representing the community in public spaces, such as using an official email address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive or unacceptable behavior may be reported to the community leaders responsible for enforcement:
|
||||
|
||||
- **Email**: [info@postgresus.com](mailto:info@postgresus.com)
|
||||
- **Telegram**: [@rostislav_dugin](https://t.me/rostislav_dugin)
|
||||
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the community.
|
||||
|
||||
## Contributing with Respect
|
||||
|
||||
When contributing to Postgresus, please:
|
||||
|
||||
- Be patient with maintainers and other contributors
|
||||
- Understand that everyone has different levels of experience
|
||||
- Ask questions in a respectful manner
|
||||
- Accept that your contribution may not be accepted, and be open to feedback
|
||||
- Follow the [contribution guidelines](https://postgresus.com/contribute)
|
||||
|
||||
For code contributions, remember to:
|
||||
|
||||
- Discuss significant changes before implementing them
|
||||
- Be open to code review feedback
|
||||
- Help review others' contributions when possible
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html).
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq).
|
||||
54
.github/SECURITY.md
vendored
Normal file
54
.github/SECURITY.md
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you discover a security vulnerability in Postgresus, please report it responsibly. **Do not create a public GitHub issue for security vulnerabilities.**
|
||||
|
||||
### How to Report
|
||||
|
||||
1. **Email** (preferred): Send details to [info@postgresus.com](mailto:info@postgresus.com)
|
||||
2. **Telegram**: Contact [@rostislav_dugin](https://t.me/rostislav_dugin)
|
||||
3. **GitHub Security Advisories**: Use the [private vulnerability reporting](https://github.com/RostislavDugin/postgresus/security/advisories/new) feature
|
||||
|
||||
### What to Include
|
||||
|
||||
- Description of the vulnerability
|
||||
- Steps to reproduce the issue
|
||||
- Potential impact and severity assessment
|
||||
- Any suggested fixes (optional)
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | --------- |
|
||||
| Latest | Yes |
|
||||
|
||||
We recommend always using the latest version of Postgresus. Security patches are applied to the most recent release.
|
||||
|
||||
### PostgreSQL Compatibility
|
||||
|
||||
Postgresus supports PostgreSQL versions 12, 13, 14, 15, 16, 17 and 18.
|
||||
|
||||
## Response Timeline
|
||||
|
||||
- **Acknowledgment**: Within 48-72 hours
|
||||
- **Initial Assessment**: Within 1 week
|
||||
- **Fix Timeline**: Depends on severity, but we aim to address critical issues as quickly as possible
|
||||
|
||||
We follow a coordinated disclosure policy. We ask that you give us reasonable time to address the vulnerability before any public disclosure.
|
||||
|
||||
## Security Features
|
||||
|
||||
Postgresus is designed with security in mind. For full details, see our [security documentation](https://postgresus.com/security).
|
||||
|
||||
Key features include:
|
||||
|
||||
- **AES-256-GCM Encryption**: Enterprise-grade encryption for backup files and sensitive data
|
||||
- **Read-Only Database Access**: Postgresus uses read-only access by default and warns if write permissions are detected
|
||||
- **Role-Based Access Control**: Assign viewer, member, admin or owner roles within workspaces
|
||||
- **Audit Logging**: Track all system activities and changes made by users
|
||||
- **Zero-Trust Storage**: Encrypted backups are safe even in shared cloud storage
|
||||
|
||||
## License
|
||||
|
||||
Postgresus is licensed under [Apache 2.0](../LICENSE).
|
||||
11
.github/workflows/ci-release.yml
vendored
11
.github/workflows/ci-release.yml
vendored
@@ -485,6 +485,17 @@ jobs:
|
||||
echo EOF
|
||||
} >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Update CITATION.cff version
|
||||
run: |
|
||||
VERSION="${{ needs.determine-version.outputs.new_version }}"
|
||||
sed -i "s/^version: .*/version: ${VERSION}/" CITATION.cff
|
||||
sed -i "s/^date-released: .*/date-released: \"$(date +%Y-%m-%d)\"/" CITATION.cff
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add CITATION.cff
|
||||
git commit -m "Update CITATION.cff to v${VERSION}" || true
|
||||
git push || true
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
|
||||
33
CITATION.cff
Normal file
33
CITATION.cff
Normal file
@@ -0,0 +1,33 @@
|
||||
cff-version: 1.2.0
|
||||
title: Postgresus
|
||||
message: "If you use this software, please cite it as below."
|
||||
type: software
|
||||
authors:
|
||||
- family-names: Dugin
|
||||
given-names: Rostislav
|
||||
repository-code: https://github.com/RostislavDugin/postgresus
|
||||
url: https://postgresus.com
|
||||
abstract: "Free, open source and self-hosted solution for automated PostgreSQL backups with multiple storage options and notifications."
|
||||
keywords:
|
||||
- docker
|
||||
- kubernetes
|
||||
- golang
|
||||
- backups
|
||||
- postgres
|
||||
- devops
|
||||
- backup
|
||||
- database
|
||||
- tools
|
||||
- monitoring
|
||||
- ftp
|
||||
- postgresql
|
||||
- s3
|
||||
- psql
|
||||
- web-ui
|
||||
- self-hosted
|
||||
- pg
|
||||
- system-administration
|
||||
- database-backup
|
||||
license: Apache-2.0
|
||||
version: 2.5.1
|
||||
date-released: "2025-06-01"
|
||||
@@ -748,13 +748,12 @@ func Test_StorageSensitiveDataLifecycle_AllTypes(t *testing.T) {
|
||||
Type: StorageTypeFTP,
|
||||
Name: "Test FTP Storage",
|
||||
FTPStorage: &ftp_storage.FTPStorage{
|
||||
Host: "ftp.example.com",
|
||||
Port: 21,
|
||||
Username: "testuser",
|
||||
Password: "original-password",
|
||||
UseSSL: false,
|
||||
PassiveMode: true,
|
||||
Path: "/backups",
|
||||
Host: "ftp.example.com",
|
||||
Port: 21,
|
||||
Username: "testuser",
|
||||
Password: "original-password",
|
||||
UseSSL: false,
|
||||
Path: "/backups",
|
||||
},
|
||||
}
|
||||
},
|
||||
@@ -765,13 +764,12 @@ func Test_StorageSensitiveDataLifecycle_AllTypes(t *testing.T) {
|
||||
Type: StorageTypeFTP,
|
||||
Name: "Updated FTP Storage",
|
||||
FTPStorage: &ftp_storage.FTPStorage{
|
||||
Host: "ftp2.example.com",
|
||||
Port: 2121,
|
||||
Username: "testuser2",
|
||||
Password: "",
|
||||
UseSSL: true,
|
||||
PassiveMode: false,
|
||||
Path: "/backups2",
|
||||
Host: "ftp2.example.com",
|
||||
Port: 2121,
|
||||
Username: "testuser2",
|
||||
Password: "",
|
||||
UseSSL: true,
|
||||
Path: "/backups2",
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
@@ -136,14 +136,13 @@ func Test_Storage_BasicOperations(t *testing.T) {
|
||||
{
|
||||
name: "FTPStorage",
|
||||
storage: &ftp_storage.FTPStorage{
|
||||
StorageID: uuid.New(),
|
||||
Host: "localhost",
|
||||
Port: ftpPort,
|
||||
Username: "testuser",
|
||||
Password: "testpassword",
|
||||
UseSSL: false,
|
||||
PassiveMode: true,
|
||||
Path: "test-files",
|
||||
StorageID: uuid.New(),
|
||||
Host: "localhost",
|
||||
Port: ftpPort,
|
||||
Username: "testuser",
|
||||
Password: "testpassword",
|
||||
UseSSL: false,
|
||||
Path: "test-files",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -16,8 +16,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
ftpConnectTimeout = 30 * time.Second
|
||||
ftpChunkSize = 16 * 1024 * 1024
|
||||
ftpConnectTimeout = 30 * time.Second
|
||||
ftpTestConnectTimeout = 10 * time.Second
|
||||
ftpChunkSize = 16 * 1024 * 1024
|
||||
)
|
||||
|
||||
type FTPStorage struct {
|
||||
@@ -29,7 +30,6 @@ type FTPStorage struct {
|
||||
Path string `json:"path" gorm:"type:text;column:path"`
|
||||
UseSSL bool `json:"useSsl" gorm:"not null;default:false;column:use_ssl"`
|
||||
SkipTLSVerify bool `json:"skipTlsVerify" gorm:"not null;default:false;column:skip_tls_verify"`
|
||||
PassiveMode bool `json:"passiveMode" gorm:"not null;default:true;column:passive_mode"`
|
||||
}
|
||||
|
||||
func (f *FTPStorage) TableName() string {
|
||||
@@ -51,7 +51,7 @@ func (f *FTPStorage) SaveFile(
|
||||
|
||||
logger.Info("Starting to save file to FTP storage", "fileId", fileID.String(), "host", f.Host)
|
||||
|
||||
conn, err := f.connect(encryptor)
|
||||
conn, err := f.connect(encryptor, ftpConnectTimeout)
|
||||
if err != nil {
|
||||
logger.Error("Failed to connect to FTP", "fileId", fileID.String(), "error", err)
|
||||
return fmt.Errorf("failed to connect to FTP: %w", err)
|
||||
@@ -114,7 +114,7 @@ func (f *FTPStorage) GetFile(
|
||||
encryptor encryption.FieldEncryptor,
|
||||
fileID uuid.UUID,
|
||||
) (io.ReadCloser, error) {
|
||||
conn, err := f.connect(encryptor)
|
||||
conn, err := f.connect(encryptor, ftpConnectTimeout)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to FTP: %w", err)
|
||||
}
|
||||
@@ -134,7 +134,7 @@ func (f *FTPStorage) GetFile(
|
||||
}
|
||||
|
||||
func (f *FTPStorage) DeleteFile(encryptor encryption.FieldEncryptor, fileID uuid.UUID) error {
|
||||
conn, err := f.connect(encryptor)
|
||||
conn, err := f.connect(encryptor, ftpConnectTimeout)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect to FTP: %w", err)
|
||||
}
|
||||
@@ -175,7 +175,10 @@ func (f *FTPStorage) Validate(encryptor encryption.FieldEncryptor) error {
|
||||
}
|
||||
|
||||
func (f *FTPStorage) TestConnection(encryptor encryption.FieldEncryptor) error {
|
||||
conn, err := f.connect(encryptor)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), ftpTestConnectTimeout)
|
||||
defer cancel()
|
||||
|
||||
conn, err := f.connectWithContext(ctx, encryptor, ftpTestConnectTimeout)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect to FTP: %w", err)
|
||||
}
|
||||
@@ -214,7 +217,6 @@ func (f *FTPStorage) Update(incoming *FTPStorage) {
|
||||
f.Username = incoming.Username
|
||||
f.UseSSL = incoming.UseSSL
|
||||
f.SkipTLSVerify = incoming.SkipTLSVerify
|
||||
f.PassiveMode = incoming.PassiveMode
|
||||
f.Path = incoming.Path
|
||||
|
||||
if incoming.Password != "" {
|
||||
@@ -222,7 +224,18 @@ func (f *FTPStorage) Update(incoming *FTPStorage) {
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FTPStorage) connect(encryptor encryption.FieldEncryptor) (*ftp.ServerConn, error) {
|
||||
func (f *FTPStorage) connect(
|
||||
encryptor encryption.FieldEncryptor,
|
||||
timeout time.Duration,
|
||||
) (*ftp.ServerConn, error) {
|
||||
return f.connectWithContext(context.Background(), encryptor, timeout)
|
||||
}
|
||||
|
||||
func (f *FTPStorage) connectWithContext(
|
||||
ctx context.Context,
|
||||
encryptor encryption.FieldEncryptor,
|
||||
timeout time.Duration,
|
||||
) (*ftp.ServerConn, error) {
|
||||
password, err := encryptor.Decrypt(f.StorageID, f.Password)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decrypt FTP password: %w", err)
|
||||
@@ -230,6 +243,9 @@ func (f *FTPStorage) connect(encryptor encryption.FieldEncryptor) (*ftp.ServerCo
|
||||
|
||||
address := fmt.Sprintf("%s:%d", f.Host, f.Port)
|
||||
|
||||
dialCtx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
|
||||
var conn *ftp.ServerConn
|
||||
if f.UseSSL {
|
||||
tlsConfig := &tls.Config{
|
||||
@@ -237,11 +253,11 @@ func (f *FTPStorage) connect(encryptor encryption.FieldEncryptor) (*ftp.ServerCo
|
||||
InsecureSkipVerify: f.SkipTLSVerify,
|
||||
}
|
||||
conn, err = ftp.Dial(address,
|
||||
ftp.DialWithTimeout(ftpConnectTimeout),
|
||||
ftp.DialWithContext(dialCtx),
|
||||
ftp.DialWithExplicitTLS(tlsConfig),
|
||||
)
|
||||
} else {
|
||||
conn, err = ftp.Dial(address, ftp.DialWithTimeout(ftpConnectTimeout))
|
||||
conn, err = ftp.Dial(address, ftp.DialWithContext(dialCtx))
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to dial FTP server: %w", err)
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
|
||||
ALTER TABLE ftp_storages
|
||||
DROP COLUMN passive_mode;
|
||||
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
|
||||
ALTER TABLE ftp_storages
|
||||
ADD COLUMN passive_mode BOOLEAN NOT NULL DEFAULT TRUE;
|
||||
|
||||
-- +goose StatementEnd
|
||||
@@ -5,6 +5,5 @@ export interface FTPStorage {
|
||||
password: string;
|
||||
useSsl: boolean;
|
||||
skipTlsVerify?: boolean;
|
||||
passiveMode: boolean;
|
||||
path?: string;
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ export const EditBackupConfigComponent = ({
|
||||
const [storages, setStorages] = useState<Storage[]>([]);
|
||||
const [isStoragesLoading, setIsStoragesLoading] = useState(false);
|
||||
const [isShowCreateStorage, setShowCreateStorage] = useState(false);
|
||||
const [storageSelectKey, setStorageSelectKey] = useState(0);
|
||||
|
||||
const [isShowWarn, setIsShowWarn] = useState(false);
|
||||
|
||||
@@ -397,6 +398,7 @@ export const EditBackupConfigComponent = ({
|
||||
<div className="mb-1 min-w-[150px] sm:mb-0">Storage</div>
|
||||
<div className="flex w-full items-center">
|
||||
<Select
|
||||
key={storageSelectKey}
|
||||
value={backupConfig.storage?.id}
|
||||
onChange={(storageId) => {
|
||||
if (storageId.includes('create-new-storage')) {
|
||||
@@ -527,7 +529,10 @@ export const EditBackupConfigComponent = ({
|
||||
title="Add storage"
|
||||
footer={<div />}
|
||||
open={isShowCreateStorage}
|
||||
onCancel={() => setShowCreateStorage(false)}
|
||||
onCancel={() => {
|
||||
setShowCreateStorage(false);
|
||||
setStorageSelectKey((prev) => prev + 1);
|
||||
}}
|
||||
>
|
||||
<div className="my-3 max-w-[275px] text-gray-500 dark:text-gray-400">
|
||||
Storage - is a place where backups will be stored (local disk, S3, Google Drive, etc.)
|
||||
|
||||
@@ -43,6 +43,7 @@ export const EditDatabaseNotifiersComponent = ({
|
||||
const [notifiers, setNotifiers] = useState<Notifier[]>([]);
|
||||
const [isNotifiersLoading, setIsNotifiersLoading] = useState(false);
|
||||
const [isShowCreateNotifier, setShowCreateNotifier] = useState(false);
|
||||
const [notifierSelectKey, setNotifierSelectKey] = useState(0);
|
||||
|
||||
const saveDatabase = async () => {
|
||||
if (!editingDatabase) return;
|
||||
@@ -104,6 +105,7 @@ export const EditDatabaseNotifiersComponent = ({
|
||||
<div className="min-w-[150px]">Notifiers</div>
|
||||
|
||||
<Select
|
||||
key={notifierSelectKey}
|
||||
mode="multiple"
|
||||
value={editingDatabase.notifiers.map((n) => n.id)}
|
||||
onChange={(notifiersIds) => {
|
||||
@@ -160,7 +162,10 @@ export const EditDatabaseNotifiersComponent = ({
|
||||
title="Add notifier"
|
||||
footer={<div />}
|
||||
open={isShowCreateNotifier}
|
||||
onCancel={() => setShowCreateNotifier(false)}
|
||||
onCancel={() => {
|
||||
setShowCreateNotifier(false);
|
||||
setNotifierSelectKey((prev) => prev + 1);
|
||||
}}
|
||||
>
|
||||
<div className="my-3 max-w-[275px] text-gray-500 dark:text-gray-400">
|
||||
Notifier - is a place where notifications will be sent (email, Slack, Telegram, etc.)
|
||||
|
||||
@@ -142,7 +142,6 @@ export function EditStorageComponent({
|
||||
username: '',
|
||||
password: '',
|
||||
useSsl: false,
|
||||
passiveMode: true,
|
||||
path: '',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -11,8 +11,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export function EditFTPStorageComponent({ storage, setStorage, setUnsaved }: Props) {
|
||||
const hasAdvancedValues =
|
||||
!!storage?.ftpStorage?.skipTlsVerify || storage?.ftpStorage?.passiveMode === false;
|
||||
const hasAdvancedValues = !!storage?.ftpStorage?.skipTlsVerify;
|
||||
const [showAdvanced, setShowAdvanced] = useState(hasAdvancedValues);
|
||||
|
||||
return (
|
||||
@@ -190,36 +189,6 @@ export function EditFTPStorageComponent({ storage, setStorage, setUnsaved }: Pro
|
||||
|
||||
{showAdvanced && (
|
||||
<>
|
||||
<div className="mb-1 flex w-full flex-col items-start sm:flex-row sm:items-center">
|
||||
<div className="mb-1 min-w-[110px] sm:mb-0">Passive mode</div>
|
||||
<div className="flex items-center">
|
||||
<Checkbox
|
||||
checked={storage?.ftpStorage?.passiveMode !== false}
|
||||
onChange={(e) => {
|
||||
if (!storage?.ftpStorage) return;
|
||||
|
||||
setStorage({
|
||||
...storage,
|
||||
ftpStorage: {
|
||||
...storage.ftpStorage,
|
||||
passiveMode: e.target.checked,
|
||||
},
|
||||
});
|
||||
setUnsaved();
|
||||
}}
|
||||
>
|
||||
Use passive mode
|
||||
</Checkbox>
|
||||
|
||||
<Tooltip
|
||||
className="cursor-pointer"
|
||||
title="Passive mode is recommended for most firewalls and NAT configurations. Disable only if you have issues connecting."
|
||||
>
|
||||
<InfoCircleOutlined className="ml-2" style={{ color: 'gray' }} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{storage?.ftpStorage?.useSsl && (
|
||||
<div className="mb-1 flex w-full flex-col items-start sm:flex-row sm:items-center">
|
||||
<div className="mb-1 min-w-[110px] sm:mb-0">Skip TLS verify</div>
|
||||
|
||||
@@ -43,13 +43,6 @@ export function ShowFTPStorageComponent({ storage }: Props) {
|
||||
Enabled
|
||||
</div>
|
||||
)}
|
||||
|
||||
{storage?.ftpStorage?.passiveMode === false && (
|
||||
<div className="mb-1 flex items-center">
|
||||
<div className="min-w-[110px]">Passive mode</div>
|
||||
Disabled
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user