mirror of
https://github.com/databasus/databasus.git
synced 2026-04-06 00:32:03 +02:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77aaabeaa1 | ||
|
|
01911dbf72 | ||
|
|
1a16f27a5d | ||
|
|
778db71625 | ||
|
|
45fc9a7fff | ||
|
|
7f5e786261 | ||
|
|
9b066bcb8a |
2
.github/workflows/ci-release.yml
vendored
2
.github/workflows/ci-release.yml
vendored
@@ -185,7 +185,7 @@ jobs:
|
||||
- name: Run Go tests
|
||||
run: |
|
||||
cd backend
|
||||
go test ./internal/...
|
||||
go test -p=1 -count=1 -failfast ./internal/...
|
||||
|
||||
- name: Stop test containers
|
||||
if: always()
|
||||
|
||||
21
README.md
21
README.md
@@ -40,13 +40,13 @@
|
||||
- **Precise timing**: run backups at specific times (e.g., 4 AM during low traffic)
|
||||
- **Smart compression**: 4-8x space savings with balanced compression (~20% overhead)
|
||||
|
||||
### 🗄️ **Multiple Storage Destinations**
|
||||
### 🗄️ **Multiple Storage Destinations** <a href="https://postgresus.com/storages">(docs)</a>
|
||||
|
||||
- **Local storage**: Keep backups on your VPS/server
|
||||
- **Cloud storage**: S3, Cloudflare R2, Google Drive, NAS, Dropbox and more
|
||||
- **Secure**: All data stays under your control
|
||||
|
||||
### 📱 **Smart Notifications**
|
||||
### 📱 **Smart Notifications** <a href="https://postgresus.com/notifiers">(docs)</a>
|
||||
|
||||
- **Multiple channels**: Email, Telegram, Slack, Discord, webhooks
|
||||
- **Real-time updates**: Success and failure notifications
|
||||
@@ -58,13 +58,20 @@
|
||||
- **SSL support**: Secure connections available
|
||||
- **Easy restoration**: One-click restore from any backup
|
||||
|
||||
### 👥 **Suitable for Teams** <a href="https://postgresus.com/access-management">(docs)</a>
|
||||
|
||||
- **Workspaces**: Group databases, notifiers and storages for different projects or teams
|
||||
- **Access management**: Control who can view or manage specific databases with role-based permissions
|
||||
- **Audit logs**: Track all system activities and changes made by users
|
||||
- **User roles**: Assign viewer, member, admin or owner roles within workspaces
|
||||
|
||||
### 🐳 **Self-Hosted & Secure**
|
||||
|
||||
- **Docker-based**: Easy deployment and management
|
||||
- **Privacy-first**: All your data stays on your infrastructure
|
||||
- **Open source**: Apache 2.0 licensed, inspect every line of code
|
||||
|
||||
### 📦 Installation
|
||||
### 📦 Installation <a href="https://postgresus.com/installation">(docs)</a>
|
||||
|
||||
You have three ways to install Postgresus:
|
||||
|
||||
@@ -118,8 +125,6 @@ This single command will:
|
||||
Create a `docker-compose.yml` file with the following configuration:
|
||||
|
||||
```yaml
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
postgresus:
|
||||
container_name: postgresus
|
||||
@@ -149,9 +154,9 @@ docker compose up -d
|
||||
6. **Add notifications** (optional): Configure email, Telegram, Slack, or webhook notifications
|
||||
7. **Save and start**: Postgresus will validate settings and begin the backup schedule
|
||||
|
||||
### 🔑 Resetting Admin Password
|
||||
### 🔑 Resetting Password <a href="https://postgresus.com/password">(docs)</a>
|
||||
|
||||
If you need to reset the admin password, you can use the built-in password reset command:
|
||||
If you need to reset the password, you can use the built-in password reset command:
|
||||
|
||||
```bash
|
||||
docker exec -it postgresus ./main --new-password="YourNewSecurePassword123" --email="admin"
|
||||
@@ -169,4 +174,4 @@ This project is licensed under the Apache 2.0 License - see the [LICENSE](LICENS
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
Contributions are welcome! Read [contributing guide](contribute/README.md) for more details, prioerities and rules are specified there. If you want to contribute, but don't know what and how - message me on Telegram [@rostislav_dugin](https://t.me/rostislav_dugin)
|
||||
Contributions are welcome! Read <a href="https://postgresus.com/contributing">contributing guide</a> for more details, prioerities and rules are specified there. If you want to contribute, but don't know what and how - message me on Telegram [@rostislav_dugin](https://t.me/rostislav_dugin)
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
@@ -94,7 +94,6 @@ func (c *BackupConfigController) GetBackupConfigByDbID(ctx *gin.Context) {
|
||||
// @Tags backup-configs
|
||||
// @Produce json
|
||||
// @Param id path string true "Storage ID"
|
||||
// @Param workspace_id query string true "Workspace ID"
|
||||
// @Success 200 {object} map[string]bool
|
||||
// @Failure 400
|
||||
// @Failure 401
|
||||
@@ -113,19 +112,7 @@ func (c *BackupConfigController) IsStorageUsing(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
workspaceIDStr := ctx.Query("workspace_id")
|
||||
if workspaceIDStr == "" {
|
||||
ctx.JSON(http.StatusBadRequest, gin.H{"error": "workspace_id query parameter is required"})
|
||||
return
|
||||
}
|
||||
|
||||
workspaceID, err := uuid.Parse(workspaceIDStr)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid workspace_id"})
|
||||
return
|
||||
}
|
||||
|
||||
isUsing, err := c.backupConfigService.IsStorageUsing(user, workspaceID, id)
|
||||
isUsing, err := c.backupConfigService.IsStorageUsing(user, id)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
|
||||
@@ -341,7 +341,7 @@ func Test_IsStorageUsing_PermissionsEnforced(t *testing.T) {
|
||||
test_utils.MakeGetRequestAndUnmarshal(
|
||||
t,
|
||||
router,
|
||||
"/api/v1/backup-configs/storage/"+storage.ID.String()+"/is-using?workspace_id="+workspace.ID.String(),
|
||||
"/api/v1/backup-configs/storage/"+storage.ID.String()+"/is-using",
|
||||
"Bearer "+testUserToken,
|
||||
tt.expectedStatusCode,
|
||||
&response,
|
||||
@@ -354,7 +354,7 @@ func Test_IsStorageUsing_PermissionsEnforced(t *testing.T) {
|
||||
testResp := test_utils.MakeGetRequest(
|
||||
t,
|
||||
router,
|
||||
"/api/v1/backup-configs/storage/"+storage.ID.String()+"/is-using?workspace_id="+workspace.ID.String(),
|
||||
"/api/v1/backup-configs/storage/"+storage.ID.String()+"/is-using",
|
||||
"Bearer "+testUserToken,
|
||||
tt.expectedStatusCode,
|
||||
)
|
||||
|
||||
@@ -119,7 +119,6 @@ func (s *BackupConfigService) GetBackupConfigByDbId(
|
||||
|
||||
func (s *BackupConfigService) IsStorageUsing(
|
||||
user *users_models.User,
|
||||
workspaceID uuid.UUID,
|
||||
storageID uuid.UUID,
|
||||
) (bool, error) {
|
||||
_, err := s.storageService.GetStorage(user, storageID)
|
||||
|
||||
@@ -271,7 +271,6 @@ func (c *DatabaseController) TestDatabaseConnectionDirect(ctx *gin.Context) {
|
||||
// @Tags databases
|
||||
// @Produce json
|
||||
// @Param id path string true "Notifier ID"
|
||||
// @Param workspace_id query string true "Workspace ID"
|
||||
// @Success 200 {object} map[string]bool
|
||||
// @Failure 400
|
||||
// @Failure 401
|
||||
@@ -290,19 +289,7 @@ func (c *DatabaseController) IsNotifierUsing(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
workspaceIDStr := ctx.Query("workspace_id")
|
||||
if workspaceIDStr == "" {
|
||||
ctx.JSON(http.StatusBadRequest, gin.H{"error": "workspace_id query parameter is required"})
|
||||
return
|
||||
}
|
||||
|
||||
workspaceID, err := uuid.Parse(workspaceIDStr)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid workspace_id"})
|
||||
return
|
||||
}
|
||||
|
||||
isUsing, err := c.databaseService.IsNotifierUsing(user, workspaceID, id)
|
||||
isUsing, err := c.databaseService.IsNotifierUsing(user, id)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
|
||||
@@ -67,6 +67,10 @@ func (p *PostgresqlDatabase) TestConnection(logger *slog.Logger) error {
|
||||
}
|
||||
|
||||
func (p *PostgresqlDatabase) HideSensitiveData() {
|
||||
if p == nil {
|
||||
return
|
||||
}
|
||||
|
||||
p.Password = ""
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@ func (d *Database) HideSensitiveData() {
|
||||
func (d *Database) Update(incoming *Database) {
|
||||
d.Name = incoming.Name
|
||||
d.Type = incoming.Type
|
||||
d.Notifiers = incoming.Notifiers
|
||||
|
||||
switch d.Type {
|
||||
case DatabaseTypePostgres:
|
||||
|
||||
@@ -219,7 +219,6 @@ func (s *DatabaseService) GetDatabasesByWorkspace(
|
||||
|
||||
func (s *DatabaseService) IsNotifierUsing(
|
||||
user *users_models.User,
|
||||
workspaceID uuid.UUID,
|
||||
notifierID uuid.UUID,
|
||||
) (bool, error) {
|
||||
_, err := s.notifierService.GetNotifier(user, notifierID)
|
||||
|
||||
@@ -27,6 +27,7 @@ type EmailNotifier struct {
|
||||
SMTPPort int `json:"smtpPort" gorm:"not null;column:smtp_port"`
|
||||
SMTPUser string `json:"smtpUser" gorm:"type:varchar(255);column:smtp_user"`
|
||||
SMTPPassword string `json:"smtpPassword" gorm:"type:varchar(255);column:smtp_password"`
|
||||
From string `json:"from" gorm:"type:varchar(255);column:from_email"`
|
||||
}
|
||||
|
||||
func (e *EmailNotifier) TableName() string {
|
||||
@@ -56,9 +57,12 @@ func (e *EmailNotifier) Validate() error {
|
||||
|
||||
func (e *EmailNotifier) Send(logger *slog.Logger, heading string, message string) error {
|
||||
// Compose email
|
||||
from := e.SMTPUser
|
||||
from := e.From
|
||||
if from == "" {
|
||||
from = "noreply@" + e.SMTPHost
|
||||
from = e.SMTPUser
|
||||
if from == "" {
|
||||
from = "noreply@" + e.SMTPHost
|
||||
}
|
||||
}
|
||||
|
||||
to := []string{e.TargetEmail}
|
||||
@@ -218,6 +222,7 @@ func (e *EmailNotifier) Update(incoming *EmailNotifier) {
|
||||
e.SMTPHost = incoming.SMTPHost
|
||||
e.SMTPPort = incoming.SMTPPort
|
||||
e.SMTPUser = incoming.SMTPUser
|
||||
e.From = incoming.From
|
||||
|
||||
if incoming.SMTPPassword != "" {
|
||||
e.SMTPPassword = incoming.SMTPPassword
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
ALTER TABLE email_notifiers
|
||||
ADD COLUMN from_email VARCHAR(255);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
ALTER TABLE email_notifiers
|
||||
DROP COLUMN from_email;
|
||||
-- +goose StatementEnd
|
||||
@@ -1,94 +0,0 @@
|
||||
### Prerequisites
|
||||
|
||||
1. Read docs in /docs folder, README.md in /backend and /frontend folders
|
||||
2. Run both backend and frontend following the instructions in their respective README.md files (for development)
|
||||
3. Read this file till the end
|
||||
|
||||
### How to create a pull request?
|
||||
|
||||
We use gitflow approach.
|
||||
|
||||
1. Create a new branch from main
|
||||
2. Make changes
|
||||
3. Create a pull request to main
|
||||
4. Wait for review
|
||||
5. Merge pull request
|
||||
|
||||
Commits should be named in the following format depending on the type of change:
|
||||
|
||||
- `FEATURE (area): What was done`
|
||||
- `FIX (area): What was fixed`
|
||||
- `REFACTOR (area): What was refactored`
|
||||
|
||||
To see examples, look at commit history in main branch.
|
||||
|
||||
Branches should be named in the following format:
|
||||
|
||||
- `feature/what_was_done`
|
||||
- `fix/what_was_fixed`
|
||||
- `refactor/what_was_refactored`
|
||||
|
||||
Example:
|
||||
|
||||
- `feature/add_support_of_kubernetes_helm`
|
||||
- `fix/make_healthcheck_optional`
|
||||
- `refactor/refactor_navbar`
|
||||
|
||||
Before any commit, make sure:
|
||||
|
||||
1. You created critical tests for your changes
|
||||
2. `make lint` is passing (for backend) and `npm run lint` is passing (for frontend)
|
||||
3. All tests are passing
|
||||
4. Project is building successfully
|
||||
5. All your commits should be squashed into one commit with proper message (or to meaningful parts)
|
||||
6. Code do really refactored and production ready
|
||||
7. You have one single PR per one feature (at least, if features not connected)
|
||||
|
||||
### Automated Versioning
|
||||
|
||||
This project uses automated versioning based on commit messages:
|
||||
|
||||
- **FEATURE (area)**: Creates a **minor** version bump (e.g., 1.0.0 → 1.1.0)
|
||||
- **FIX (area)**: Creates a **patch** version bump (e.g., 1.0.0 → 1.0.1)
|
||||
- **REFACTOR (area)**: Creates a **patch** version bump (e.g., 1.0.0 → 1.0.1)
|
||||
- **BREAKING CHANGE**: Creates a **major** version bump (e.g., 1.0.0 → 2.0.0)
|
||||
|
||||
The system automatically:
|
||||
|
||||
- Analyzes commits since the last release
|
||||
- Determines the appropriate version bump
|
||||
- Generates a changelog grouped by area (frontend/backend/etc.)
|
||||
- Creates GitHub releases with detailed release notes
|
||||
- Updates package.json version numbers
|
||||
|
||||
To skip automated release (for documentation updates, etc.), add `[skip-release]` to your commit message.
|
||||
|
||||
### Docs
|
||||
|
||||
If you need to add some explanation, do it in appropriate place in the code. Or in the /docs folder if it is something general. For charts, use Mermaid.
|
||||
|
||||
### Priorities
|
||||
|
||||
Before taking anything more than a couple of lines of code, please write Rostislav via Telegram (@rostislav_dugin) and confirm priority. It is possible that we already have something in the works, it is not needed or it's not project priority.
|
||||
|
||||
Nearsest features:
|
||||
- add API keys and API actions
|
||||
- add encryption
|
||||
|
||||
Storages tasks:
|
||||
- check AWS S3 support
|
||||
- check Google Cloud S3 support
|
||||
- add FTP
|
||||
- add Dropbox
|
||||
- add OneDrive
|
||||
- add NAS
|
||||
- add Yandex Drive
|
||||
|
||||
Notifications tasks:
|
||||
- add Mattermost
|
||||
- make webhooks flexible
|
||||
- add Gotify
|
||||
|
||||
Extra:
|
||||
|
||||
- add HTTPS for Postgresus
|
||||
@@ -1,45 +0,0 @@
|
||||
# How to add new notifier to Postgresus (Discord, Slack, Telegram, Email, Webhook, etc.)
|
||||
|
||||
## Backend part
|
||||
|
||||
1. Create new model in `backend/internal/features/notifiers/models/{notifier_name}/` folder. Implement `NotificationSender` interface from parent folder.
|
||||
- The model should implement `Send(logger *slog.Logger, heading string, message string) error` and `Validate() error` methods
|
||||
- Use UUID primary key as `NotifierID` that references the main notifiers table
|
||||
|
||||
2. Add new notifier type to `backend/internal/features/notifiers/enums.go` in the `NotifierType` constants.
|
||||
|
||||
3. Update the main `Notifier` model in `backend/internal/features/notifiers/model.go`:
|
||||
- Add new notifier field with GORM foreign key relation
|
||||
- Update `getSpecificNotifier()` method to handle the new type
|
||||
- Update `Send()` method to route to the new notifier
|
||||
|
||||
4. If you need to add some .env variables to test, add them in `backend/internal/config/config.go` (so we can use it in tests)
|
||||
|
||||
5. If you need some Docker container to test, add it to `backend/docker-compose.yml.example`. For sensitive data - keep it blank.
|
||||
|
||||
6. If you need some sensitive envs to test in pipeline, message @rostislav_dugin so I can add it to GitHub Actions. For example, API keys or credentials.
|
||||
|
||||
7. Create new migration in `backend/migrations` folder:
|
||||
- Create table with `notifier_id` as UUID primary key
|
||||
- Add foreign key constraint to `notifiers` table with CASCADE DELETE
|
||||
- Look at existing notifier migrations for reference
|
||||
|
||||
8. Make sure that all tests are passing.
|
||||
|
||||
## Frontend part
|
||||
|
||||
If you are able to develop only backend - it's fine, message @rostislav_dugin so I can complete UI part.
|
||||
|
||||
1. Add models and validator to `frontend/src/entity/notifiers/models/{notifier_name}/` folder and update `index.ts` file to include new model exports.
|
||||
|
||||
2. Upload an SVG icon to `public/icons/notifiers/`, update `src/entity/notifiers/models/getNotifierLogoFromType.ts` to return new icon path, update `src/entity/notifiers/models/NotifierType.ts` to include new type, and update `src/entity/notifiers/models/getNotifierNameFromType.ts` to return new name.
|
||||
|
||||
3. Add UI components to manage your notifier:
|
||||
- `src/features/notifiers/ui/edit/notifiers/Edit{NotifierName}Component.tsx` (for editing)
|
||||
- `src/features/notifiers/ui/show/notifier/Show{NotifierName}Component.tsx` (for display)
|
||||
|
||||
4. Update main components to handle the new notifier type:
|
||||
- `EditNotifierComponent.tsx` - add import, validation function, and component rendering
|
||||
- `ShowNotifierComponent.tsx` - add import and component rendering
|
||||
|
||||
5. Make sure everything is working as expected.
|
||||
@@ -1,51 +0,0 @@
|
||||
# How to add new storage to Postgresus (S3, FTP, Google Drive, NAS, etc.)
|
||||
|
||||
## Backend part
|
||||
|
||||
1. Create new model in `backend/internal/features/storages/models/{storage_name}/` folder. Implement `StorageFileSaver` interface from parent folder.
|
||||
- The model should implement `SaveFile(logger *slog.Logger, fileID uuid.UUID, file io.Reader) error`, `GetFile(fileID uuid.UUID) (io.ReadCloser, error)`, `DeleteFile(fileID uuid.UUID) error`, `Validate() error`, and `TestConnection() error` methods
|
||||
- Use UUID primary key as `StorageID` that references the main storages table
|
||||
- Add `TableName() string` method to return the proper table name
|
||||
|
||||
2. Add new storage type to `backend/internal/features/storages/enums.go` in the `StorageType` constants.
|
||||
|
||||
3. Update the main `Storage` model in `backend/internal/features/storages/model.go`:
|
||||
- Add new storage field with GORM foreign key relation
|
||||
- Update `getSpecificStorage()` method to handle the new type
|
||||
- Update `SaveFile()`, `GetFile()`, and `DeleteFile()` methods to route to the new storage
|
||||
- Update `Validate()` method to include new storage validation
|
||||
|
||||
4. If you need to add some .env variables to test, add them in `backend/internal/config/config.go` (so we can use it in tests)
|
||||
|
||||
5. If you need some Docker container to test, add it to `backend/docker-compose.yml.example`. For sensitive data - keep it blank.
|
||||
|
||||
6. If you need some sensitive envs to test in pipeline, message @rostislav_dugin so I can add it to GitHub Actions. For example, Google Drive envs or FTP credentials.
|
||||
|
||||
7. Create new migration in `backend/migrations` folder:
|
||||
- Create table with `storage_id` as UUID primary key
|
||||
- Add foreign key constraint to `storages` table with CASCADE DELETE
|
||||
- Look at existing storage migrations for reference
|
||||
|
||||
8. Update tests in `backend/internal/features/storages/model_test.go` to test new storage
|
||||
|
||||
9. Make sure that all tests are passing.
|
||||
|
||||
## Frontend part
|
||||
|
||||
If you are able to develop only backend - it's fine, message @rostislav_dugin so I can complete UI part.
|
||||
|
||||
1. Add models and api to `frontend/src/entity/storages/models/` folder and update `index.ts` file to include new model exports.
|
||||
- Create TypeScript interface for your storage model
|
||||
- Add validation function if needed
|
||||
|
||||
2. Upload an SVG icon to `public/icons/storages/`, update `src/entity/storages/models/getStorageLogoFromType.ts` to return new icon path, update `src/entity/storages/models/StorageType.ts` to include new type, and update `src/entity/storages/models/getStorageNameFromType.ts` to return new name.
|
||||
|
||||
3. Add UI components to manage your storage:
|
||||
- `src/features/storages/ui/edit/storages/Edit{StorageName}Component.tsx` (for editing)
|
||||
- `src/features/storages/ui/show/storages/Show{StorageName}Component.tsx` (for display)
|
||||
|
||||
4. Update main components to handle the new storage type:
|
||||
- `EditStorageComponent.tsx` - add import and component rendering
|
||||
- `ShowStorageComponent.tsx` - add import and component rendering
|
||||
|
||||
5. Make sure everything is working as expected.
|
||||
@@ -4,4 +4,5 @@ export interface EmailNotifier {
|
||||
smtpPort: number;
|
||||
smtpUser: string;
|
||||
smtpPassword: string;
|
||||
from: string;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import { getUserTimeFormat } from '../../../shared/time';
|
||||
import { ConfirmationComponent } from '../../../shared/ui';
|
||||
import { RestoresComponent } from '../../restores';
|
||||
|
||||
const BACKUPS_PAGE_SIZE = 10;
|
||||
const BACKUPS_PAGE_SIZE = 50;
|
||||
|
||||
interface Props {
|
||||
database: Database;
|
||||
|
||||
@@ -111,6 +111,7 @@ export function EditNotifierComponent({
|
||||
smtpPort: 0,
|
||||
smtpUser: '',
|
||||
smtpPassword: '',
|
||||
from: '',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -108,6 +108,7 @@ export function EditEmailNotifierComponent({ notifier, setNotifier, setIsUnsaved
|
||||
<div className="mb-1 flex items-center">
|
||||
<div className="w-[130px] min-w-[130px]">SMTP password</div>
|
||||
<Input
|
||||
type="password"
|
||||
value={notifier?.emailNotifier?.smtpPassword || ''}
|
||||
onChange={(e) => {
|
||||
if (!notifier?.emailNotifier) return;
|
||||
@@ -126,6 +127,35 @@ export function EditEmailNotifierComponent({ notifier, setNotifier, setIsUnsaved
|
||||
placeholder="password"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-1 flex items-center">
|
||||
<div className="w-[130px] min-w-[130px]">From</div>
|
||||
<Input
|
||||
value={notifier?.emailNotifier?.from || ''}
|
||||
onChange={(e) => {
|
||||
if (!notifier?.emailNotifier) return;
|
||||
|
||||
setNotifier({
|
||||
...notifier,
|
||||
emailNotifier: {
|
||||
...notifier.emailNotifier,
|
||||
from: e.target.value.trim(),
|
||||
},
|
||||
});
|
||||
setIsUnsaved(true);
|
||||
}}
|
||||
size="small"
|
||||
className="w-full max-w-[250px]"
|
||||
placeholder="example@example.com"
|
||||
/>
|
||||
|
||||
<Tooltip
|
||||
className="cursor-pointer"
|
||||
title="Optional. Email address to use as sender. If empty, will use SMTP user or auto-generate from host"
|
||||
>
|
||||
<InfoCircleOutlined className="ml-2" style={{ color: 'gray' }} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export function EditSlackNotifierComponent({ notifier, setNotifier, setIsUnsaved
|
||||
<div className="mb-1 ml-[130px] max-w-[200px]" style={{ lineHeight: 1 }}>
|
||||
<a
|
||||
className="text-xs !text-blue-600"
|
||||
href="https://postgresus.com/notifier-slack"
|
||||
href="https://postgresus.com/notifiers/slack"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
|
||||
@@ -30,7 +30,7 @@ export function EditTeamsNotifierComponent({ notifier, setNotifier, setIsUnsaved
|
||||
<div className="mb-1 ml-[130px] max-w-[200px]" style={{ lineHeight: 1 }}>
|
||||
<a
|
||||
className="text-xs !text-blue-600"
|
||||
href="https://postgresus.com/notifier-teams"
|
||||
href="https://postgresus.com/notifiers/teams"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
|
||||
@@ -31,6 +31,11 @@ export function ShowEmailNotifierComponent({ notifier }: Props) {
|
||||
<div className="min-w-[110px]">SMTP password</div>
|
||||
{'*************'}
|
||||
</div>
|
||||
|
||||
<div className="mb-1 flex items-center">
|
||||
<div className="min-w-[110px]">From</div>
|
||||
{notifier?.emailNotifier?.from || '(auto)'}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ export function SettingsComponent({ contentHeight }: Props) {
|
||||
<div className="mt-3 text-sm text-gray-500">
|
||||
Read more about settings you can{' '}
|
||||
<a
|
||||
href="https://postgresus.com/settings"
|
||||
href="https://postgresus.com/access-management/#global-settings"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="!text-blue-600"
|
||||
@@ -230,23 +230,18 @@ export function SettingsComponent({ contentHeight }: Props) {
|
||||
<code
|
||||
className="flex-1 cursor-pointer transition-colors select-all hover:text-blue-600"
|
||||
onClick={() => {
|
||||
window.open(
|
||||
`${getApplicationServer()}/api/v1/downdetect/is-available`,
|
||||
'_blank',
|
||||
);
|
||||
window.open(`${getApplicationServer()}/api/v1/system/health`, '_blank');
|
||||
}}
|
||||
title="Click to open in new tab"
|
||||
>
|
||||
{getApplicationServer()}/api/v1/downdetect/is-available
|
||||
{getApplicationServer()}/api/v1/system/health
|
||||
</code>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
className="ml-2 opacity-0 transition-opacity group-hover:opacity-100"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(
|
||||
`${getApplicationServer()}/api/v1/downdetect/is-available`,
|
||||
);
|
||||
navigator.clipboard.writeText(`${getApplicationServer()}/api/v1/system/health`);
|
||||
message.success('Health-check endpoint copied to clipboard');
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -40,7 +40,7 @@ export function EditGoogleDriveStorageComponent({ storage, setStorage, setIsUnsa
|
||||
<div className="min-w-[110px]" />
|
||||
|
||||
<div className="text-xs text-blue-600">
|
||||
<a href="https://postgresus.com/google-drive-storage" target="_blank" rel="noreferrer">
|
||||
<a href="https://postgresus.com/storages/google-drive" target="_blank" rel="noreferrer">
|
||||
How to connect Google Drive?
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,7 @@ export function EditS3StorageComponent({ storage, setStorage, setIsUnsaved }: Pr
|
||||
<div className="min-w-[110px]" />
|
||||
|
||||
<div className="text-xs text-blue-600">
|
||||
<a href="https://postgresus.com/cloudflare-r2-storage" target="_blank" rel="noreferrer">
|
||||
<a href="https://postgresus.com/storages/cloudflare-r2" target="_blank" rel="noreferrer">
|
||||
How to use with Cloudflare R2?
|
||||
</a>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user