feat: add settings seed action and remove login credentials

SuperAdmins can seed once from Settings, and the login page no longer exposes default credentials.
This commit is contained in:
2026-08-14 23:23:06 +03:30
parent 35dfd4a603
commit 833584b1b4
7 changed files with 160 additions and 24 deletions
+2 -22
View File
@@ -10,19 +10,6 @@
<p class="text-muted text-sm mt-2 mb-0">{{ $t('auth.welcome') }}</p>
</div>
<!-- Credentials Hint Box -->
<div class="surface-ground p-3 border-round border-1 border-color mb-4 text-xs">
<div class="font-bold text-primary mb-1 flex align-items-center gap-1">
<i class="pi pi-key"></i>
<span>اطلاعات ورود مدیر سیستم:</span>
</div>
<div class="flex justify-content-between align-items-center mt-1">
<span>نام کاربری: <code class="font-bold text-color">superadmin</code></span>
<span>رمز عبور: <code class="font-bold text-color">SuperAdminSecret123!</code></span>
</div>
<Button label="جاگذاری اطلاعات حساب مدیر" size="small" text class="w-full mt-2 text-xs p-1" @click="fillDemo" />
</div>
<form @submit.prevent="handleLogin" class="flex flex-column gap-3">
<div class="flex flex-column gap-2">
<label for="username" class="font-semibold text-sm text-color">{{ $t('auth.username') }}</label>
@@ -33,7 +20,6 @@
v-model.trim="form.username"
class="w-full text-sm"
:class="{ 'p-invalid': errors.username }"
placeholder="superadmin"
autocomplete="username"
/>
</IconField>
@@ -52,7 +38,6 @@
class="w-full text-sm"
inputClass="w-full text-sm"
:class="{ 'p-invalid': errors.password }"
placeholder="SuperAdminSecret123!"
autocomplete="current-password"
/>
</IconField>
@@ -90,8 +75,8 @@ const { showSuccess, showError } = useToast();
const { t } = useI18n();
const form = reactive({
username: 'superadmin',
password: 'SuperAdminSecret123!'
username: '',
password: ''
});
const errors = reactive({
@@ -101,11 +86,6 @@ const errors = reactive({
const loading = ref(false);
const fillDemo = () => {
form.username = 'superadmin';
form.password = 'SuperAdminSecret123!';
};
const validate = () => {
let valid = true;
errors.username = '';
+122
View File
@@ -0,0 +1,122 @@
<!-- /src/views/settings/SettingsView.vue -->
<template>
<div class="settings-view w-full max-w-3xl mx-auto">
<PageHeader
title="تنظیمات سیستم"
subtitle="عملیات راه‌اندازی و پیکربندی اولیه پایگاه داده"
/>
<div class="surface-card p-4 sm:p-5 border-round-xl border-1 border-color shadow-sm">
<div class="flex align-items-start gap-3 mb-4">
<div class="w-3rem h-3rem border-round-xl flex align-items-center justify-content-center flex-shrink-0 bg-primary-light text-primary">
<i class="pi pi-database text-xl"></i>
</div>
<div class="flex-grow-1">
<h2 class="text-lg font-bold text-color m-0 mb-1">راهاندازی پایگاه داده</h2>
<p class="text-sm text-muted m-0 line-height-3">
نقشهای سیستمی و حساب مدیر ارشد را یکبار ایجاد میکند. پس از اجرا، این دکمه برای همیشه مخفی میشود.
</p>
</div>
</div>
<div v-if="isStatusLoading" class="flex align-items-center gap-2 text-muted text-sm">
<i class="pi pi-spin pi-spinner"></i>
<span>در حال بررسی وضعیت راهاندازی</span>
</div>
<div v-else-if="isLocked" class="flex align-items-center gap-2 p-3 border-round surface-ground border-1 border-color">
<i class="pi pi-lock text-primary"></i>
<div class="flex flex-column gap-1">
<span class="font-semibold text-sm text-color">راهاندازی اولیه انجام شده است</span>
<span v-if="lockedAtLabel" class="text-xs text-muted">{{ lockedAtLabel }}</span>
</div>
</div>
<div v-else class="flex flex-column sm:flex-row align-items-stretch sm:align-items-center gap-3">
<Button
label="اجرای راه‌اندازی پایگاه داده"
icon="pi pi-play"
class="font-bold"
:loading="isSeeding"
@click="confirmSeed"
/>
<span class="text-xs text-muted line-height-3">
این عملیات فقط یکبار قابل اجرا است.
</span>
</div>
</div>
</div>
</template>
<script setup>
import { computed, onMounted, ref } from 'vue';
import { useConfirm } from 'primevue/useconfirm';
import Button from 'primevue/button';
import PageHeader from '@/components/common/PageHeader.vue';
import { seedApi } from '@/api/seedApi';
import { useToast } from '@/composables/useToast';
const confirm = useConfirm();
const { showSuccess, showError } = useToast();
const isStatusLoading = ref(true);
const isSeeding = ref(false);
const isLocked = ref(false);
const lockedAt = ref(null);
const lockedAtLabel = computed(() => {
if (!lockedAt.value) return '';
const date = new Date(lockedAt.value);
if (Number.isNaN(date.getTime())) return '';
return `قفل شده در ${date.toLocaleString('fa-IR')}`;
});
const applyStatus = (payload) => {
const status = payload?.data || payload || {};
isLocked.value = Boolean(status.locked);
lockedAt.value = status.lockedAt || null;
};
const loadStatus = async () => {
isStatusLoading.value = true;
try {
const response = await seedApi.getStatus();
applyStatus(response);
} catch (err) {
showError(err);
} finally {
isStatusLoading.value = false;
}
};
const runSeed = async () => {
isSeeding.value = true;
try {
const response = await seedApi.runSeed();
applyStatus({ locked: true, lockedAt: new Date().toISOString(), ...(response?.data || response) });
isLocked.value = true;
showSuccess('راه‌اندازی پایگاه داده با موفقیت انجام شد.');
} catch (err) {
showError(err);
await loadStatus();
} finally {
isSeeding.value = false;
}
};
const confirmSeed = () => {
confirm.require({
header: 'راه‌اندازی پایگاه داده',
message: 'نقش‌های سیستمی و حساب مدیر ارشد ایجاد یا به‌روز می‌شوند. این دکمه پس از اجرا دیگر نمایش داده نمی‌شود. ادامه می‌دهید؟',
icon: 'pi pi-exclamation-triangle',
acceptLabel: 'اجرا',
rejectLabel: 'انصراف',
acceptClass: 'p-button-primary',
accept: () => {
runSeed();
}
});
};
onMounted(loadStatus);
</script>