Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c77c52271c | |||
| 7e00ed3151 | |||
| 39c9666c0f | |||
| 8eb240440b | |||
| 9dc6fe6eeb | |||
| acfa24d742 | |||
| 88f6b77455 | |||
| bc5e08142a | |||
| 373839cf03 | |||
| 8e31309c55 |
+11
@@ -11850,6 +11850,17 @@ def test_push_notification():
|
||||
return jsonify({'success': False}), 500
|
||||
|
||||
|
||||
@app.route('/batch_upload', methods=['GET'])
|
||||
def batch_upload_page():
|
||||
"""
|
||||
Serves the HTML frontend for the batch CSV and image upload.
|
||||
"""
|
||||
# Check permissions if necessary, similar to your other routes
|
||||
if 'username' not in session:
|
||||
flash('Bitte melden Sie sich an.', 'error')
|
||||
return redirect(url_for('login'))
|
||||
|
||||
return render_template('upload_batch.html')
|
||||
|
||||
def clean_db_field(val):
|
||||
"""Bereinigt Werte, die fälschlicherweise als String-Listen oder mit Klammern aus der CSV kommen."""
|
||||
|
||||
@@ -54,10 +54,34 @@ def _clean_name_fragment(value):
|
||||
return cleaned
|
||||
|
||||
|
||||
def _get_tenant_db(client):
|
||||
def _get_tenant_db(client, tenant_id=None):
|
||||
"""Return the current tenant database for the request, or fall back to default."""
|
||||
try:
|
||||
from tenant import get_tenant_db
|
||||
if tenant_id is None:
|
||||
return get_tenant_db(client)
|
||||
|
||||
tenant_id = str(tenant_id).strip()
|
||||
if tenant_id:
|
||||
try:
|
||||
from tenant import get_tenant_config
|
||||
|
||||
config = get_tenant_config(tenant_id)
|
||||
if isinstance(config, dict):
|
||||
explicit_db = config.get('db') or config.get('db_name')
|
||||
if explicit_db:
|
||||
db_name = str(explicit_db).strip()
|
||||
if db_name and not db_name.startswith('inventar_'):
|
||||
db_name = f'inventar_{db_name}'
|
||||
if db_name:
|
||||
return client[db_name]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
sanitized = ''.join(c for c in tenant_id.lower() if c.isalnum() or c == '_')
|
||||
if sanitized:
|
||||
return client[f'inventar_{sanitized}']
|
||||
|
||||
return get_tenant_db(client)
|
||||
except Exception:
|
||||
return client[cfg.MONGODB_DB]
|
||||
@@ -479,8 +503,45 @@ def check_nm_pwd(username, password):
|
||||
query = {'$or': [{'Username': username}, {'username': username}]}
|
||||
user_record_fallback = users.find_one(query)
|
||||
if user_record_fallback is None:
|
||||
logger.warning("Kein Benutzer für %r in DB %r gefunden.", dp.encrypt_text(username), db_name)
|
||||
return None
|
||||
if db_name != cfg.MONGODB_DB:
|
||||
default_users = client[cfg.MONGODB_DB]['users']
|
||||
user_record_fallback = default_users.find_one(
|
||||
{'$or': [
|
||||
{'Username': dp.encrypt_text(username)},
|
||||
{'username': dp.encrypt_text(username)},
|
||||
{'Username': username},
|
||||
{'username': username},
|
||||
]}
|
||||
)
|
||||
if user_record_fallback is not None:
|
||||
migrated_record = dict(user_record_fallback)
|
||||
migrated_record.pop('_id', None)
|
||||
users.replace_one(
|
||||
{'$or': [
|
||||
{'Username': migrated_record.get('Username')},
|
||||
{'username': migrated_record.get('username')},
|
||||
]},
|
||||
migrated_record,
|
||||
upsert=True,
|
||||
)
|
||||
logger.warning(
|
||||
"Tenant user %r migrated from default DB %r to tenant DB %r.",
|
||||
dp.encrypt_text(username),
|
||||
cfg.MONGODB_DB,
|
||||
db_name,
|
||||
)
|
||||
user_record = users.find_one(
|
||||
{'$or': [
|
||||
{'Username': migrated_record.get('Username')},
|
||||
{'username': migrated_record.get('username')},
|
||||
]}
|
||||
) or migrated_record
|
||||
else:
|
||||
logger.warning("Kein Benutzer für %r in DB %r gefunden.", dp.encrypt_text(username), db_name)
|
||||
return None
|
||||
else:
|
||||
logger.warning("Kein Benutzer für %r in DB %r gefunden.", dp.encrypt_text(username), db_name)
|
||||
return None
|
||||
else:
|
||||
user_record = user_record_fallback
|
||||
|
||||
@@ -497,6 +558,59 @@ def check_nm_pwd(username, password):
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
def add_admin(
|
||||
username,
|
||||
password,
|
||||
name='',
|
||||
last_name='',
|
||||
is_student=False,
|
||||
permission_preset='full_access',
|
||||
action_permissions=None,
|
||||
page_permissions=None,
|
||||
tenant_id=None,
|
||||
):
|
||||
"""
|
||||
Add a new user to the database.
|
||||
"""
|
||||
if not check_password_strength(password):
|
||||
return False
|
||||
|
||||
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||
try:
|
||||
db = _get_tenant_db(client, tenant_id)
|
||||
users = db['users']
|
||||
|
||||
permission_defaults = build_default_permission_payload(permission_preset)
|
||||
|
||||
if isinstance(action_permissions, dict):
|
||||
for key, value in action_permissions.items():
|
||||
permission_defaults['actions'][str(key)] = bool(value)
|
||||
|
||||
if isinstance(page_permissions, dict):
|
||||
for key, value in page_permissions.items():
|
||||
permission_defaults['pages'][str(key)] = bool(value)
|
||||
|
||||
safe_name = name.strip() if name else ''
|
||||
safe_last_name = last_name.strip() if last_name else ''
|
||||
|
||||
user_doc = {
|
||||
'Username': username,
|
||||
'Password': hashing(password),
|
||||
'Admin': True,
|
||||
'active_ausleihung': None,
|
||||
'name': dp.encrypt_text(safe_name) if safe_name else '',
|
||||
'last_name': dp.encrypt_text(safe_last_name) if safe_last_name else '',
|
||||
'IsStudent': bool(is_student),
|
||||
'PermissionPreset': permission_defaults['preset'],
|
||||
'ActionPermissions': permission_defaults['actions'],
|
||||
'PagePermissions': permission_defaults['pages'],
|
||||
}
|
||||
|
||||
users.insert_one(user_doc)
|
||||
return True
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
def add_user(
|
||||
username,
|
||||
@@ -509,6 +623,7 @@ def add_user(
|
||||
permission_preset='standard_user',
|
||||
action_permissions=None,
|
||||
page_permissions=None,
|
||||
tenant_id=None,
|
||||
):
|
||||
"""
|
||||
Add a new user to the database.
|
||||
@@ -518,7 +633,7 @@ def add_user(
|
||||
|
||||
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||
try:
|
||||
db = _get_tenant_db(client)
|
||||
db = _get_tenant_db(client, tenant_id)
|
||||
users = db['users']
|
||||
|
||||
permission_defaults = build_default_permission_payload(permission_preset)
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 1. Ein echter CSV-Parser, der Zeilenumbrüche und Kommas in Texten korrekt ignoriert
|
||||
// 1. Robuster CSV-Parser, der Zeilenumbrüche und Kommas in Texten korrekt ignoriert
|
||||
function parseCSV(csvString) {
|
||||
const rows = [];
|
||||
let currentRow = [];
|
||||
@@ -70,7 +70,6 @@ function parseCSV(csvString) {
|
||||
} else if ((char === '\n' || char === '\r') && !insideQuotes) {
|
||||
if (char === '\r' && nextChar === '\n') i++; // Windows Umbrüche überspringen
|
||||
currentRow.push(currentCell);
|
||||
// Leere Zeilen ignorieren
|
||||
if (currentRow.length > 1 || currentRow[0] !== '') {
|
||||
rows.push(currentRow);
|
||||
}
|
||||
@@ -94,7 +93,6 @@ function rowToCSV(rowArray) {
|
||||
return rowArray.map(cell => {
|
||||
if (cell === null || cell === undefined) return '';
|
||||
let cellStr = String(cell);
|
||||
// Wenn kritische Zeichen drin sind, sauber in Anführungszeichen verpacken
|
||||
if (cellStr.includes(',') || cellStr.includes('"') || cellStr.includes('\n') || cellStr.includes('\r')) {
|
||||
return '"' + cellStr.replace(/"/g, '""') + '"';
|
||||
}
|
||||
@@ -102,7 +100,7 @@ function rowToCSV(rowArray) {
|
||||
}).join(',');
|
||||
}
|
||||
|
||||
// 3. Bildnamen extrahieren (bleibt wie es war)
|
||||
// 3. Bildnamen extrahieren
|
||||
function extractImageNames(cellValue) {
|
||||
if (!cellValue) return [];
|
||||
let raw = String(cellValue).replace(/^["']|["']$/g, '').trim();
|
||||
@@ -155,11 +153,10 @@ document.getElementById('batchUploadForm').addEventListener('submit', async func
|
||||
imageMap.set(file.name.toLowerCase(), file);
|
||||
});
|
||||
|
||||
const BATCH_SIZE = 50;
|
||||
const BATCH_SIZE = 20;
|
||||
|
||||
try {
|
||||
const csvText = await csvFile.text();
|
||||
// Hier rufen wir jetzt unseren sicheren Parser auf!
|
||||
const allRows = parseCSV(csvText);
|
||||
|
||||
if (allRows.length <= 1) {
|
||||
@@ -171,10 +168,8 @@ document.getElementById('batchUploadForm').addEventListener('submit', async func
|
||||
|
||||
log(`${dataRows.length} Einträge gefunden. Bereite Batches vor...`);
|
||||
|
||||
// Spalten-Index von "Images" sicher ermitteln
|
||||
const imagesColIndex = headerRow.findIndex(h => h.toLowerCase().trim() === 'images');
|
||||
|
||||
// In Batches aufteilen
|
||||
const batches = [];
|
||||
for (let i = 0; i < dataRows.length; i += BATCH_SIZE) {
|
||||
batches.push(dataRows.slice(i, i + BATCH_SIZE));
|
||||
@@ -183,74 +178,77 @@ document.getElementById('batchUploadForm').addEventListener('submit', async func
|
||||
progressBar.max = batches.length;
|
||||
progressBar.value = 0;
|
||||
|
||||
// Sequenzieller Upload mit Fehlertoleranz pro Batch
|
||||
for (let b = 0; b < batches.length; b++) {
|
||||
const batchRows = batches[b];
|
||||
progressText.textContent = `Lade Batch ${b + 1} von ${batches.length} hoch...`;
|
||||
|
||||
const requiredImagesForBatch = new Set();
|
||||
try {
|
||||
const requiredImagesForBatch = new Set();
|
||||
|
||||
if (imagesColIndex !== -1) {
|
||||
batchRows.forEach(rowCols => {
|
||||
if (rowCols[imagesColIndex]) {
|
||||
const imgNames = extractImageNames(rowCols[imagesColIndex]);
|
||||
imgNames.forEach(name => {
|
||||
const fileMatch = imageMap.get(name.toLowerCase());
|
||||
if (fileMatch) {
|
||||
requiredImagesForBatch.add(fileMatch);
|
||||
}
|
||||
});
|
||||
if (imagesColIndex !== -1) {
|
||||
batchRows.forEach(rowCols => {
|
||||
if (rowCols[imagesColIndex]) {
|
||||
const imgNames = extractImageNames(rowCols[imagesColIndex]);
|
||||
imgNames.forEach(name => {
|
||||
const fileMatch = imageMap.get(name.toLowerCase());
|
||||
if (fileMatch) {
|
||||
requiredImagesForBatch.add(fileMatch);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const batchCsvArray = [headerRow, ...batchRows];
|
||||
const batchCsvText = batchCsvArray.map(rowToCSV).join('\n');
|
||||
const batchCsvBlob = new Blob([batchCsvText], { type: 'text/csv' });
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('csv_file', batchCsvBlob, `batch_${b + 1}.csv`);
|
||||
|
||||
if (csrfToken) {
|
||||
formData.append('csrf_token', csrfToken);
|
||||
}
|
||||
|
||||
requiredImagesForBatch.forEach(imgFile => {
|
||||
formData.append('images', imgFile);
|
||||
});
|
||||
|
||||
log(`Batch ${b + 1}: ${batchRows.length} Items & ${requiredImagesForBatch.size} zugehörige Bilder.`);
|
||||
|
||||
const response = await fetch('/upload_csv_batch', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
'X-CSRFToken': csrfToken || ''
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Sicherer Zusammenbau des CSV-Batches
|
||||
const batchCsvArray = [headerRow, ...batchRows];
|
||||
const batchCsvText = batchCsvArray.map(rowToCSV).join('\n');
|
||||
const batchCsvBlob = new Blob([batchCsvText], { type: 'text/csv' });
|
||||
const responseText = await response.text();
|
||||
let result;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('csv_file', batchCsvBlob, `batch_${b + 1}.csv`);
|
||||
|
||||
if (csrfToken) {
|
||||
formData.append('csrf_token', csrfToken);
|
||||
}
|
||||
|
||||
requiredImagesForBatch.forEach(imgFile => {
|
||||
formData.append('images', imgFile);
|
||||
});
|
||||
|
||||
log(`Batch ${b + 1}: ${batchRows.length} Items & ${requiredImagesForBatch.size} zugehörige Bilder.`);
|
||||
|
||||
// Request absenden
|
||||
const response = await fetch('/upload_csv_batch', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
'X-CSRFToken': csrfToken || ''
|
||||
try {
|
||||
result = JSON.parse(responseText);
|
||||
} catch (parseErr) {
|
||||
throw new Error(`Server-Fehler (Status ${response.status}). HTML statt JSON erhalten.`);
|
||||
}
|
||||
});
|
||||
|
||||
// 1. Antwort als rohen Text auslesen (verhindert den JSON.parse Crash)
|
||||
const responseText = await response.text();
|
||||
let result;
|
||||
if (!response.ok || !result.success) {
|
||||
throw new Error(result.message || `Server-Fehler ${response.status}`);
|
||||
}
|
||||
|
||||
try {
|
||||
result = JSON.parse(responseText);
|
||||
} catch (parseErr) {
|
||||
// Wenn der Server kein JSON schickt (z.B. Python 500 Error als HTML-Seite)
|
||||
console.error("Server-Antwort war kein JSON:", responseText);
|
||||
throw new Error(`Server-Fehler (Status ${response.status}). Der Server hat ein HTML-Dokument statt JSON zurückgegeben. Prüfe die Flask-Konsole!`);
|
||||
log(`Batch ${b + 1} erfolgreich abgeschlossen.`);
|
||||
|
||||
} catch (batchErr) {
|
||||
log(`⚠️ FEHLER in Batch ${b + 1}: ${batchErr.message}. Überspringe und fahre fort...`);
|
||||
console.error(`Batch ${b + 1} fehlgeschlagen:`, batchErr);
|
||||
}
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
throw new Error(result.message || `Server-Fehler ${response.status}`);
|
||||
}
|
||||
|
||||
log(`Batch ${b + 1} abgeschlossen: ${result.message}`);
|
||||
progressBar.value = b + 1;
|
||||
}
|
||||
|
||||
progressText.textContent = "Upload erfolgreich beendet! Keine verschobenen Spalten mehr.";
|
||||
progressText.textContent = "Upload-Prozess beendet!";
|
||||
uploadBtn.disabled = false;
|
||||
|
||||
} catch (err) {
|
||||
|
||||
+48
-85
@@ -201,65 +201,35 @@ sys.path.insert(0, "/app/Web")
|
||||
from Web.modules.database import settings
|
||||
from pymongo import MongoClient
|
||||
import Web.modules.inventarsystem.data_protection as dp
|
||||
import Web.modules.database.user as us
|
||||
|
||||
tenant_id = sys.argv[1].lower()
|
||||
mode = sys.argv[2]
|
||||
sanitized = "".join(c for c in tenant_id if c.isalnum() or c == "_")
|
||||
db_name = f"inventar_{sanitized}"
|
||||
client = MongoClient(settings.MONGODB_HOST, int(settings.MONGODB_PORT))
|
||||
db_name = f"inventar_{sanitized}" if sanitized else settings.MONGODB_DB
|
||||
|
||||
client = MongoClient(settings.MONGODB_HOST, settings.MONGODB_PORT)
|
||||
db = client[db_name]
|
||||
|
||||
pw_bytes = "admin123".encode("utf-8")
|
||||
random_salt = os.urandom(16)
|
||||
hashed = hashlib.scrypt(pw_bytes, salt=random_salt, n=16384, r=8, p=1)
|
||||
hashed_pw_string = f"v1${random_salt.hex()}${hashed.hex()}"
|
||||
|
||||
action_permissions = {
|
||||
"can_borrow": True,
|
||||
"can_insert": True,
|
||||
"can_edit": True,
|
||||
"can_delete": True,
|
||||
"can_manage_users": True,
|
||||
"can_manage_settings": True,
|
||||
"can_view_logs": True,
|
||||
users = db["users"]
|
||||
permission_defaults = us.build_default_permission_payload("full_access")
|
||||
admin_doc = {
|
||||
"Username": "admin",
|
||||
"Password": us.hashing("admin123"),
|
||||
"Admin": True,
|
||||
"active_ausleihung": None,
|
||||
"name": dp.encrypt_text("admin"),
|
||||
"last_name": dp.encrypt_text("admin"),
|
||||
"IsStudent": False,
|
||||
"PermissionPreset": permission_defaults["preset"],
|
||||
"ActionPermissions": permission_defaults["actions"],
|
||||
"PagePermissions": permission_defaults["pages"],
|
||||
}
|
||||
|
||||
page_permissions = {
|
||||
"home": True,
|
||||
"tutorial_page": True,
|
||||
"my_borrowed_items": True,
|
||||
"notifications_view": True,
|
||||
"impressum": True,
|
||||
"license": True,
|
||||
"library_view": True,
|
||||
"terminplan": True,
|
||||
"home_admin": True,
|
||||
"upload_admin": True,
|
||||
"library_admin": True,
|
||||
"admin_borrowings": True,
|
||||
"library_loans_admin": True,
|
||||
"admin_damaged_items": True,
|
||||
"admin_audit_dashboard": True,
|
||||
"logs": True,
|
||||
"manage_filters": True,
|
||||
"manage_locations": True,
|
||||
}
|
||||
|
||||
if db.users.count_documents({"Username": dp.encrypt_text("admin")}) == 0:
|
||||
db.users.insert_one({
|
||||
"Username": dp.encrypt_text("admin"),
|
||||
"Password": hashed_pw_string,
|
||||
"Admin": True,
|
||||
"active_ausleihung": None,
|
||||
"name": dp.encrypt_text("Admin"),
|
||||
"last_name": dp.encrypt_text("User"),
|
||||
"IsStudent": False,
|
||||
"PermissionPreset": "full_access",
|
||||
"ActionPermissions": action_permissions,
|
||||
"PagePermissions": page_permissions,
|
||||
})
|
||||
users.replace_one({"Username": "admin"}, admin_doc, upsert=True)
|
||||
print("Fallback successfully applied")
|
||||
|
||||
if mode == "trial":
|
||||
client = MongoClient(settings.MONGODB_HOST, settings.MONGODB_PORT)
|
||||
db = client[db_name]
|
||||
db.settings.update_one(
|
||||
{"setting_type": "tenant_trial"},
|
||||
{"$set": {
|
||||
@@ -272,6 +242,8 @@ if mode == "trial":
|
||||
upsert=True,
|
||||
)
|
||||
|
||||
client.close()
|
||||
|
||||
print(f"Tenant {sys.argv[1]} database initialized. Default admin: admin / admin123")
|
||||
PY
|
||||
}
|
||||
@@ -643,11 +615,11 @@ case "$COMMAND" in
|
||||
initialize_tenant_database "$TENANT_ID" "trial"
|
||||
echo "Trial tenant '$TENANT_ID' successfully configured. It will expire after $DAYS_ARG day(s) and self-delete."
|
||||
;;
|
||||
|
||||
|
||||
remove)
|
||||
FORCE_REMOVE=false
|
||||
TENANT_ARG="${2:-}"
|
||||
|
||||
|
||||
if [ "$TENANT_ARG" = "--yes" ] || [ "$TENANT_ARG" = "-y" ]; then
|
||||
FORCE_REMOVE=true
|
||||
TENANT_ID="${3:-}"
|
||||
@@ -672,40 +644,31 @@ case "$COMMAND" in
|
||||
echo "Removing tenant '$TENANT_ID'..."
|
||||
APP_CONTAINER=$(docker ps -qf "name=app" | head -n 1)
|
||||
port_to_remove=""
|
||||
|
||||
|
||||
if [ -n "$APP_CONTAINER" ]; then
|
||||
# Zuerst die Datenbank via PyMongo hart droppen (Erzwungenes Löschen)
|
||||
port_to_remove="$(docker exec "$APP_CONTAINER" python3 - "$TENANT_ID" <<'PY'
|
||||
import sys, re
|
||||
sys.path.insert(0, '/app')
|
||||
sys.path.insert(0, '/app/Web')
|
||||
from tenant import delete_tenant, get_tenant_config
|
||||
from Web.modules.database import settings
|
||||
from pymongo import MongoClient
|
||||
# MongoDB-Datenbank via PyMongo direkt im Container droppen
|
||||
docker exec -i "$APP_CONTAINER" python3 -c '
|
||||
import sys, os
|
||||
try:
|
||||
import pymongo
|
||||
tenant_id = sys.argv[1]
|
||||
mongo_uri = os.environ.get("MONGO_URI", "mongodb://localhost:27017/")
|
||||
client = pymongo.MongoClient(mongo_uri, serverSelectionTimeoutMS=2000)
|
||||
db_name = f"inventar_{tenant_id}"
|
||||
if db_name in client.list_database_names():
|
||||
client.drop_database(db_name)
|
||||
print(f"Dropped database: {db_name}")
|
||||
except Exception as e:
|
||||
print(f"Error dropping database: {e}", file=sys.stderr)
|
||||
' "$TENANT_ID" > /dev/null 2>&1
|
||||
|
||||
tenant_id = sys.argv[1]
|
||||
tenant_cfg = get_tenant_config(tenant_id)
|
||||
port = tenant_cfg.get('port')
|
||||
# Konfiguration und Port via Host-Funktion bereinigen und Port ermitteln
|
||||
if port_to_remove="$(remove_tenant_port "$TENANT_ID" 2>/dev/null)"; then
|
||||
:
|
||||
else
|
||||
port_to_remove=""
|
||||
fi
|
||||
|
||||
# Datenbanknamen exakt rekonstruieren
|
||||
sanitized = "".join(c for c in tenant_id if c.isalnum() or c == "_")
|
||||
db_name = f"inventar_{sanitized}"
|
||||
|
||||
try:
|
||||
client = MongoClient(settings.MONGODB_HOST, int(settings.MONGODB_PORT))
|
||||
client.drop_database(db_name)
|
||||
print(f"MongoDB database '{db_name}' dropped successfully.", file=sys.stderr)
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not drop database '{db_name}': {e}", file=sys.stderr)
|
||||
|
||||
if not delete_tenant(tenant_id):
|
||||
print(f'Error: failed to delete tenant {tenant_id}', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if port is not None:
|
||||
print(port)
|
||||
PY
|
||||
)"
|
||||
echo "Tenant '$TENANT_ID' database and config removed."
|
||||
else
|
||||
echo "Warning: Application container not running. Tenant database may still exist in MongoDB."
|
||||
@@ -728,7 +691,7 @@ PY
|
||||
else
|
||||
echo "Removed tenant '$TENANT_ID'. No port mapping was present."
|
||||
fi
|
||||
;;
|
||||
;;
|
||||
|
||||
restart-tenant)
|
||||
TENANT_ID="${2:-}"
|
||||
|
||||
Reference in New Issue
Block a user