feat(monitoring): add Redis exporter + Sentry docs to deployment guide
Some checks failed
CI / ruff (push) Successful in 10s
CI / pytest (push) Failing after 47m30s
CI / validate (push) Successful in 24s
CI / dependency-scanning (push) Successful in 29s
CI / docs (push) Has been skipped
CI / deploy (push) Has been skipped

- Add redis-exporter container to docker-compose (oliver006/redis_exporter, 32MB)
- Add Redis scrape target to Prometheus config
- Add 4 Redis alert rules: RedisDown, HighMemory, HighConnections, RejectedConnections
- Document Step 19b (Sentry Error Tracking) in Hetzner deployment guide
- Document Step 19c (Redis Monitoring) in Hetzner deployment guide
- Update resource budget and port reference tables

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-27 23:30:18 +01:00
parent ce822af883
commit 35d1559162
54 changed files with 664 additions and 343 deletions

View File

@@ -49,7 +49,7 @@ apiClient.interceptors.response.use(
};
throw apiError;
}
// Handle network errors
if (error.code === 'ECONNABORTED') {
throw {
@@ -58,7 +58,7 @@ apiClient.interceptors.response.use(
statusCode: 408
};
}
throw {
errorCode: 'NETWORK_ERROR',
message: 'Network error. Please check your connection.',
@@ -105,7 +105,7 @@ class ApiClient {
if (error.errorCode) {
throw error; // Re-throw API errors
}
// Handle network errors
throw {
errorCode: 'NETWORK_ERROR',
@@ -130,28 +130,28 @@ export const ERROR_MESSAGES = {
INVALID_CREDENTIALS: 'Invalid username or password. Please try again.',
TOKEN_EXPIRED: 'Your session has expired. Please log in again.',
USER_NOT_ACTIVE: 'Your account has been deactivated. Contact support.',
// MarketplaceProduct errors
PRODUCT_NOT_FOUND: 'MarketplaceProduct not found. It may have been removed.',
PRODUCT_ALREADY_EXISTS: 'A product with this ID already exists.',
INVALID_PRODUCT_DATA: 'Please check the product information and try again.',
// Inventory errors
INSUFFICIENT_INVENTORY: 'Not enough inventory available for this operation.',
INVENTORY_NOT_FOUND: 'No inventory information found for this product.',
NEGATIVE_INVENTORY_NOT_ALLOWED: 'Inventory quantity cannot be negative.',
// Shop errors
SHOP_NOT_FOUND: 'Shop not found or no longer available.',
UNAUTHORIZED_SHOP_ACCESS: 'You do not have permission to access this shop.',
SHOP_ALREADY_EXISTS: 'A shop with this code already exists.',
MAX_SHOPS_REACHED: 'You have reached the maximum number of shops allowed.',
// Import errors
IMPORT_JOB_NOT_FOUND: 'Import job not found.',
IMPORT_JOB_CANNOT_BE_CANCELLED: 'This import job cannot be cancelled at this time.',
MARKETPLACE_CONNECTION_FAILED: 'Failed to connect to marketplace. Please try again.',
// Generic fallbacks
VALIDATION_ERROR: 'Please check your input and try again.',
NETWORK_ERROR: 'Connection error. Please check your internet connection.',
@@ -179,7 +179,7 @@ export const useApiError = () => {
const handleApiCall = async (apiCall) => {
setIsLoading(true);
setError(null);
try {
const result = await apiCall();
setIsLoading(false);
@@ -233,7 +233,7 @@ const ProductForm = () => {
if (apiError.field) {
setFieldErrors({ [apiError.field]: apiError.message });
}
// Handle specific error codes
switch (apiError.errorCode) {
case 'PRODUCT_ALREADY_EXISTS':
@@ -312,7 +312,7 @@ class ErrorBoundary extends React.Component {
componentDidCatch(error, errorInfo) {
console.error('Error caught by boundary:', error, errorInfo);
// Log to error reporting service
if (window.errorReporting) {
window.errorReporting.captureException(error, {
@@ -442,7 +442,7 @@ const handleInventoryUpdate = async (gtin, location, quantity) => {
switch (error.errorCode) {
case 'INSUFFICIENT_INVENTORY':
const { available_quantity, requested_quantity } = error.details;
notificationManager.notify('error',
notificationManager.notify('error',
`Cannot remove ${requested_quantity} items. Only ${available_quantity} available.`
);
break;
@@ -493,7 +493,7 @@ const ImportJobStatus = ({ jobId }) => {
switch (error.errorCode) {
case 'IMPORT_JOB_CANNOT_BE_CANCELLED':
const { current_status } = error.details;
notificationManager.notify('error',
notificationManager.notify('error',
`Cannot cancel job in ${current_status} status`
);
break;
@@ -549,12 +549,12 @@ export const logError = (error, context = {}) => {
try {
const existingErrors = JSON.parse(localStorage.getItem('apiErrors') || '[]');
existingErrors.push(errorData);
// Keep only last 50 errors
if (existingErrors.length > 50) {
existingErrors.splice(0, existingErrors.length - 50);
}
localStorage.setItem('apiErrors', JSON.stringify(existingErrors));
} catch (e) {
console.warn('Failed to store error locally:', e);

View File

@@ -292,7 +292,7 @@ Add this script **before** Alpine.js in your HTML pages:
<span>Options</span>
<span x-html="$icon('chevron-down', 'w-4 h-4 ml-2')"></span>
</button>
<div x-show="open" class="absolute right-0 mt-2 w-48 bg-white rounded-lg shadow-lg">
<a href="#" class="flex items-center px-4 py-2 hover:bg-gray-100">
<span x-html="$icon('edit', 'w-4 h-4 mr-2')"></span>
@@ -425,4 +425,4 @@ The icon system works in all modern browsers:
- [Heroicons Official Site](https://heroicons.com/)
- [Tailwind CSS Documentation](https://tailwindcss.com/docs)
- [Alpine.js Magic Properties](https://alpinejs.dev/magics/el)
- [Alpine.js Magic Properties](https://alpinejs.dev/magics/el)

View File

@@ -213,13 +213,13 @@ For complex changes that require data transformation:
def upgrade() -> None:
# Create new column
op.add_column('products', sa.Column('normalized_price', sa.Numeric(10, 2)))
# Migrate data
connection = op.get_bind()
connection.execute(
text("UPDATE products SET normalized_price = CAST(price AS NUMERIC) WHERE price ~ '^[0-9.]+$'")
)
# Make column non-nullable after data migration
op.alter_column('products', 'normalized_price', nullable=False)
@@ -329,7 +329,7 @@ def upgrade() -> None:
if context.get_x_argument(as_dictionary=True).get('dev_data', False):
# Add development sample data
pass
# Always apply schema changes
op.create_table(...)
```
@@ -345,20 +345,20 @@ For large data transformations, use batch processing:
```python
def upgrade() -> None:
connection = op.get_bind()
# Process in batches to avoid memory issues
batch_size = 1000
offset = 0
while True:
result = connection.execute(
text(f"SELECT id, old_field FROM products LIMIT {batch_size} OFFSET {offset}")
)
rows = result.fetchall()
if not rows:
break
for row in rows:
# Transform data
new_value = transform_function(row.old_field)
@@ -366,7 +366,7 @@ def upgrade() -> None:
text("UPDATE products SET new_field = :new_val WHERE id = :id"),
{"new_val": new_value, "id": row.id}
)
offset += batch_size
```

View File

@@ -45,7 +45,7 @@ C:\ProgramData\chocolatey\bin\make.exe %*
1. Open PyCharm Settings: **File → Settings** (Ctrl+Alt+S)
2. Navigate to: **Build, Execution, Deployment → Build Tools → Make**
3. Set the **Path to make executable** to: `[YOUR_PROJECT_PATH]\make-venv.bat`
For example: `E:\Letzshop-Import-v1\make-venv.bat`
4. Click **Apply** and **OK**
@@ -112,4 +112,4 @@ your-project/
├── Makefile
├── make-venv.bat
└── requirements.txt
```
```

View File

@@ -11,11 +11,11 @@ Git is an open-source, distributed version control system that helps you manage
## 🛠️ Create a Git Repository
1. **Sign in to DSM** using an account with administrative privileges.
2. Go to:
**Control Panel > Terminal & SNMP > Terminal**
2. Go to:
**Control Panel > Terminal & SNMP > Terminal**
→ Enable **SSH service**.
3. Go to:
**Control Panel > Shared Folder**
3. Go to:
**Control Panel > Shared Folder**
→ Create a shared folder for Git repositories.
4. On your computer, access Synology NAS via SSH:
@@ -63,7 +63,7 @@ Adding new repository on NAS DS223J
> chown -R git-boulaht1:users my-repo.git
> cd my-repo.git/
```
---
### ✅ **Steps to Push Local Git Repo to Synology NAS**
@@ -110,9 +110,9 @@ and then Permission and then tick boxes apply to sub folder and files
(git-boulaht1 should be read write on the folder)
> ⚠️ **Note:**
> Do **not** perform the above commands with root permissions.
> Git Server no longer supports `git-shell` commands due to security concerns.
> ⚠️ **Note:**
> Do **not** perform the above commands with root permissions.
> Git Server no longer supports `git-shell` commands due to security concerns.
> For `git-shell` access, consider using container-based Git services.
---
@@ -149,4 +149,4 @@ and then Permission and then tick boxes apply to sub folder and files
cd /volume1/mysharefolder/myrepo1
```
---
---