feat(monitoring): add Redis exporter + Sentry docs to deployment guide
Some checks failed
Some checks failed
- 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:
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
|
||||
@@ -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
|
||||
```
|
||||
```
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
---
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user