return req.AdminPassword == adminPassword
}
-// GetAllUsers returns all users with their disk usage
-func GetAllUsers(w http.ResponseWriter, r *http.Request) {
+// GetAdminData returns:
+// - all users with their disk usage
+// - free disk space
+// - migration-info
+func GetAdminData(w http.ResponseWriter, r *http.Request) {
if !validateAdminPasswordInRequest(r) {
http.Error(w, "Invalid admin password", http.StatusUnauthorized)
return
freeSpace = 0 // Default to 0 if we can't determine free space
}
+ // Check for old directory and get old users info
+ oldDirInfo := getOldDirectoryInfo()
+
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]any{
"users": adminUsers,
"free_space": freeSpace,
+ "old_data": oldDirInfo,
})
}
return freeSpace, nil
}
+// getOldDirectoryInfo checks for old directory and returns info about old users and directory size
+func getOldDirectoryInfo() map[string]any {
+ oldDirPath := filepath.Join(utils.Settings.DataPath, "old")
+
+ // Check if old directory exists
+ if _, err := os.Stat(oldDirPath); os.IsNotExist(err) {
+ return map[string]any{
+ "exists": false,
+ }
+ }
+
+ // Read users.json from old directory
+ usersJsonPath := filepath.Join(oldDirPath, "users.json")
+ var oldUsernames []string
+
+ if _, err := os.Stat(usersJsonPath); err == nil {
+ // Read and parse users.json
+ data, err := os.ReadFile(usersJsonPath)
+ if err == nil {
+ var usersData map[string]any
+ if json.Unmarshal(data, &usersData) == nil {
+ if usersList, ok := usersData["users"].([]any); ok {
+ for _, u := range usersList {
+ if user, ok := u.(map[string]any); ok {
+ if username, ok := user["username"].(string); ok {
+ oldUsernames = append(oldUsernames, username)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Calculate total size of old directory
+ var totalSize int64
+ filepath.Walk(oldDirPath, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return nil // Continue on errors
+ }
+ if !info.IsDir() {
+ totalSize += info.Size()
+ }
+ return nil
+ })
+
+ return map[string]any{
+ "exists": true,
+ "usernames": oldUsernames,
+ "total_size": totalSize,
+ }
+}
+
// DeleteUser deletes a user and all their data
func DeleteUser(w http.ResponseWriter, r *http.Request) {
var req struct {
"success": true,
})
}
+
+// DeleteOldData deletes the entire old directory
+func DeleteOldData(w http.ResponseWriter, r *http.Request) {
+ if !validateAdminPasswordInRequest(r) {
+ http.Error(w, "Invalid admin password", http.StatusUnauthorized)
+ return
+ }
+
+ oldDirPath := filepath.Join(utils.Settings.DataPath, "old")
+
+ // Check if old directory exists
+ if _, err := os.Stat(oldDirPath); os.IsNotExist(err) {
+ http.Error(w, "Old directory does not exist", http.StatusNotFound)
+ return
+ }
+
+ // Remove the entire old directory
+ if err := os.RemoveAll(oldDirPath); err != nil {
+ log.Printf("Error deleting old directory: %v", err)
+ http.Error(w, "Error deleting old directory", http.StatusInternalServerError)
+ return
+ }
+
+ log.Printf("Old directory successfully deleted by admin (id: %d, username: %s)", r.Context().Value(utils.UserIDKey), r.Context().Value(utils.UsernameKey))
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(map[string]bool{
+ "success": true,
+ })
+}
// Admin data
let freeSpace = $state(0);
+ let oldData = $state({});
let users = $state([]);
let isLoadingUsers = $state(false);
let deleteUserId = $state(null);
let isDeletingUser = $state(false);
+ let confirmDeleteOldData = $state(false);
+ let isDeletingOldData = $state(false);
+
onMount(() => {
currentUser = localStorage.getItem('user');
resetAdminState();
isLoadingUsers = true;
try {
- const response = await makeAdminApiCall('/admin/users');
+ const response = await makeAdminApiCall('/admin/get-data');
users = response.data.users || [];
freeSpace = response.data.free_space;
+ oldData = response.data.old_data;
} catch (error) {
console.error('Error loading users:', error);
if (error.response?.status === 401) {
function confirmDeleteUser(userId) {
deleteUserId = deleteUserId === userId ? null : userId;
}
+
+ // Delete old data directory
+ async function deleteOldData() {
+ if (isDeletingOldData) return;
+ isDeletingOldData = true;
+
+ try {
+ const response = await makeAdminApiCall('/admin/delete-old-data');
+ if (response.data.success) {
+ // Reset old data state
+ oldData = { exists: false };
+ confirmDeleteOldData = false;
+
+ // Show success toast
+ const toast = new bootstrap.Toast(document.getElementById('toastSuccessOldDataDelete'));
+ toast.show();
+ } else {
+ // Show error toast
+ const toast = new bootstrap.Toast(document.getElementById('toastErrorOldDataDelete'));
+ toast.show();
+ }
+ } catch (error) {
+ console.error('Error deleting old data:', error);
+ if (error.response?.status === 401) {
+ resetAdminState();
+ } else {
+ // Show error toast
+ const toast = new bootstrap.Toast(document.getElementById('toastErrorOldDataDelete'));
+ toast.show();
+ }
+ } finally {
+ isDeletingOldData = false;
+ }
+ }
+
+ function toggleDeleteOldDataConfirmation() {
+ confirmDeleteOldData = !confirmDeleteOldData;
+ }
</script>
<div class="settings-admin">
</div>
</div>
{/if}
+ </div>
+ </div>
- <div class="mt-3">
- <button class="btn btn-outline-primary" onclick={loadUsers} disabled={isLoadingUsers}>
- {#if isLoadingUsers}
- <span class="spinner-border spinner-border-sm me-2"></span>
+ <!-- Old Data Card -->
+ {#if oldData.exists}
+ <div class="card mt-4">
+ <div class="card-header">
+ <h4 class="card-title mb-0">📦 {$t('settings.admin.old_data')}</h4>
+ </div>
+ <div class="card-body">
+ <p class="text-muted mb-3">
+ {@html $t('settings.admin.old_data_description')}
+ </p>
+
+ {#if oldData.usernames && oldData.usernames.length > 0}
+ <h6>{$t('settings.admin.old_users')}:</h6>
+ <div class="mb-3">
+ {#each oldData.usernames as username, index}
+ <span class="badge bg-secondary me-1">{username}</span>
+ {/each}
+ </div>
+ {:else}
+ <p class="text-warning">
+ {$t('settings.admin.no_old_users_found')}
+ </p>
+ {/if}
+
+ <div class="row">
+ <div class="col-md-6">
+ <strong>{@html $t('settings.admin.old_data_size')}: </strong>
+ {formatBytes(oldData.total_size)}
+ </div>
+ </div>
+
+ <!-- Delete old data button -->
+ <div class="mt-3">
+ <button
+ class="btn btn-danger"
+ onclick={toggleDeleteOldDataConfirmation}
+ disabled={isDeletingOldData}
+ >
+ 🗑️ {$t('settings.admin.delete_old_data')}
+ </button>
+
+ {#if confirmDeleteOldData}
+ <div class="mt-3" transition:slide>
+ <div class="alert alert-danger">
+ <p class="mb-2">
+ <strong>{$t('settings.admin.confirm_delete_old_data')}</strong><br />
+ {@html $t('settings.admin.delete_old_data_warning')}
+ </p>
+ <div class="d-flex gap-2">
+ <button
+ class="btn btn-secondary btn-sm"
+ onclick={toggleDeleteOldDataConfirmation}
+ >
+ {$t('settings.admin.cancel')}
+ </button>
+ <button
+ class="btn btn-danger btn-sm"
+ onclick={deleteOldData}
+ disabled={isDeletingOldData}
+ >
+ {#if isDeletingOldData}
+ <span class="spinner-border spinner-border-sm me-1"></span>
+ {/if}
+ {$t('settings.admin.delete')}
+ </button>
+ </div>
+ </div>
+ </div>
{/if}
- {$t('settings.admin.refresh_users')}
- </button>
+ </div>
</div>
</div>
+ {/if}
+
+ <!-- Reload Button moved to bottom -->
+ <div class="mt-4 d-flex justify-content-center">
+ <button class="btn btn-outline-primary" onclick={loadUsers} disabled={isLoadingUsers}>
+ {#if isLoadingUsers}
+ <span class="spinner-border spinner-border-sm me-2"></span>
+ {/if}
+ {$t('settings.admin.refresh_users')}
+ </button>
</div>
</div>
{/if}
<div class="toast-body">
{$t('settings.statistics.toast_error_user_delete')}
</div>
+ <button
+ type="button"
+ class="btn-close me-2 m-auto"
+ data-bs-dismiss="toast"
+ aria-label="Close"
+ ></button>
+ </div>
+ </div>
+
+ <div
+ id="toastSuccessOldDataDelete"
+ class="toast align-items-center text-bg-success"
+ role="alert"
+ aria-live="assertive"
+ aria-atomic="true"
+ >
+ <div class="d-flex">
+ <div class="toast-body">
+ {$t('settings.admin.toast_success_old_data_delete')}
+ </div>
+ <button
+ type="button"
+ class="btn-close me-2 m-auto"
+ data-bs-dismiss="toast"
+ aria-label="Close"
+ ></button>
+ </div>
+ </div>
+
+ <div
+ id="toastErrorOldDataDelete"
+ class="toast align-items-center text-bg-danger"
+ role="alert"
+ aria-live="assertive"
+ aria-atomic="true"
+ >
+ <div class="d-flex">
+ <div class="toast-body">
+ {$t('settings.admin.toast_error_old_data_delete')}
+ </div>
+ <button
+ type="button"
+ class="btn-close me-2 m-auto"
+ data-bs-dismiss="toast"
+ aria-label="Close"
+ ></button>
</div>
</div>
</div>