forked from morrning/hesabixCore
577 lines
20 KiB
Vue
577 lines
20 KiB
Vue
|
|
<template>
|
|||
|
|
<div>
|
|||
|
|
<!-- هدر اصلی -->
|
|||
|
|
<div class="d-flex align-center mb-8">
|
|||
|
|
<div class="d-flex align-center bg-primary-lighten-5 pa-4 rounded-lg">
|
|||
|
|
<v-icon size="32" color="primary" class="mr-3">mdi-oauth</v-icon>
|
|||
|
|
<div>
|
|||
|
|
<h3 class="text-h5 font-weight-medium text-primary mb-1">برنامههای OAuth</h3>
|
|||
|
|
<p class="text-caption text-medium-emphasis mb-0">مدیریت برنامههای احراز هویت خارجی</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- توضیحات OAuth -->
|
|||
|
|
<v-alert
|
|||
|
|
type="info"
|
|||
|
|
variant="tonal"
|
|||
|
|
class="mb-6"
|
|||
|
|
>
|
|||
|
|
<template v-slot:prepend>
|
|||
|
|
<v-icon>mdi-information</v-icon>
|
|||
|
|
</template>
|
|||
|
|
<div class="text-right">
|
|||
|
|
<strong>OAuth چیست؟</strong><br>
|
|||
|
|
OAuth یک پروتکل استاندارد برای احراز هویت است که به برنامههای خارجی اجازه میدهد
|
|||
|
|
بدون نیاز به رمز عبور، به حساب کاربران دسترسی داشته باشند. این روش امنتر و راحتتر از
|
|||
|
|
روشهای سنتی است.
|
|||
|
|
</div>
|
|||
|
|
</v-alert>
|
|||
|
|
|
|||
|
|
<!-- کارت مدیریت برنامهها -->
|
|||
|
|
<v-card variant="outlined" class="pa-6" elevation="0">
|
|||
|
|
<v-card-title class="text-subtitle-1 font-weight-medium pb-4 d-flex align-center justify-space-between">
|
|||
|
|
<div class="d-flex align-center">
|
|||
|
|
<v-icon start class="mr-2" color="primary">mdi-oauth</v-icon>
|
|||
|
|
مدیریت برنامهها
|
|||
|
|
</div>
|
|||
|
|
<v-btn
|
|||
|
|
color="primary"
|
|||
|
|
prepend-icon="mdi-plus"
|
|||
|
|
@click="showCreateDialog = true"
|
|||
|
|
>
|
|||
|
|
برنامه جدید
|
|||
|
|
</v-btn>
|
|||
|
|
</v-card-title>
|
|||
|
|
|
|||
|
|
<v-card-text class="pa-0">
|
|||
|
|
<v-alert
|
|||
|
|
v-if="applications.length === 0"
|
|||
|
|
type="info"
|
|||
|
|
variant="tonal"
|
|||
|
|
class="mb-4"
|
|||
|
|
>
|
|||
|
|
هنوز هیچ برنامه OAuth ایجاد نکردهاید. برای شروع، یک برنامه جدید ایجاد کنید.
|
|||
|
|
</v-alert>
|
|||
|
|
|
|||
|
|
<v-row v-else>
|
|||
|
|
<v-col
|
|||
|
|
v-for="app in applications"
|
|||
|
|
:key="app.id"
|
|||
|
|
cols="12"
|
|||
|
|
md="6"
|
|||
|
|
lg="4"
|
|||
|
|
>
|
|||
|
|
<v-card
|
|||
|
|
variant="outlined"
|
|||
|
|
class="h-100 oauth-card"
|
|||
|
|
:class="{ 'oauth-card-active': app.isActive, 'oauth-card-inactive': !app.isActive }"
|
|||
|
|
>
|
|||
|
|
<v-card-title class="d-flex justify-space-between align-center">
|
|||
|
|
<div class="d-flex align-center">
|
|||
|
|
<v-avatar
|
|||
|
|
:color="app.isActive ? 'success' : 'grey'"
|
|||
|
|
size="32"
|
|||
|
|
class="mr-2"
|
|||
|
|
>
|
|||
|
|
<v-icon size="16" color="white">
|
|||
|
|
{{ app.isActive ? 'mdi-check' : 'mdi-close' }}
|
|||
|
|
</v-icon>
|
|||
|
|
</v-avatar>
|
|||
|
|
{{ app.name }}
|
|||
|
|
</div>
|
|||
|
|
<v-menu>
|
|||
|
|
<template v-slot:activator="{ props }">
|
|||
|
|
<v-btn
|
|||
|
|
icon="mdi-dots-vertical"
|
|||
|
|
variant="text"
|
|||
|
|
v-bind="props"
|
|||
|
|
></v-btn>
|
|||
|
|
</template>
|
|||
|
|
<v-list>
|
|||
|
|
<v-list-item @click="editApplication(app)" class="oauth-menu-item">
|
|||
|
|
<template v-slot:prepend>
|
|||
|
|
<v-icon>mdi-pencil</v-icon>
|
|||
|
|
</template>
|
|||
|
|
<v-list-item-title>ویرایش</v-list-item-title>
|
|||
|
|
</v-list-item>
|
|||
|
|
<v-list-item @click="showStats(app)" class="oauth-menu-item">
|
|||
|
|
<template v-slot:prepend>
|
|||
|
|
<v-icon>mdi-chart-line</v-icon>
|
|||
|
|
</template>
|
|||
|
|
<v-list-item-title>آمار</v-list-item-title>
|
|||
|
|
</v-list-item>
|
|||
|
|
<v-list-item @click="regenerateSecret(app)" class="oauth-menu-item">
|
|||
|
|
<template v-slot:prepend>
|
|||
|
|
<v-icon>mdi-refresh</v-icon>
|
|||
|
|
</template>
|
|||
|
|
<v-list-item-title>بازسازی کلید</v-list-item-title>
|
|||
|
|
</v-list-item>
|
|||
|
|
<v-list-item @click="revokeTokens(app)" class="oauth-menu-item">
|
|||
|
|
<template v-slot:prepend>
|
|||
|
|
<v-icon>mdi-logout</v-icon>
|
|||
|
|
</template>
|
|||
|
|
<v-list-item-title>لغو توکنها</v-list-item-title>
|
|||
|
|
</v-list-item>
|
|||
|
|
<v-divider></v-divider>
|
|||
|
|
<v-list-item @click="deleteApplication(app)" color="error" class="oauth-menu-item">
|
|||
|
|
<template v-slot:prepend>
|
|||
|
|
<v-icon color="error">mdi-delete</v-icon>
|
|||
|
|
</template>
|
|||
|
|
<v-list-item-title class="text-error">حذف</v-list-item-title>
|
|||
|
|
</v-list-item>
|
|||
|
|
</v-list>
|
|||
|
|
</v-menu>
|
|||
|
|
</v-card-title>
|
|||
|
|
|
|||
|
|
<v-card-text>
|
|||
|
|
<div v-if="app.description" class="mb-3 oauth-info-text">
|
|||
|
|
{{ app.description }}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="text-caption text-grey">
|
|||
|
|
<div class="mb-1">
|
|||
|
|
<strong>Client ID:</strong>
|
|||
|
|
<span class="oauth-code">{{ app.clientId }}</span>
|
|||
|
|
<v-btn
|
|||
|
|
icon="mdi-content-copy"
|
|||
|
|
size="x-small"
|
|||
|
|
variant="text"
|
|||
|
|
@click="copyToClipboard(app.clientId)"
|
|||
|
|
></v-btn>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="mb-1">
|
|||
|
|
<strong>Redirect URI:</strong> {{ app.redirectUri }}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="mb-1">
|
|||
|
|
<strong>وضعیت:</strong>
|
|||
|
|
<v-chip
|
|||
|
|
:color="app.isActive ? 'success' : 'error'"
|
|||
|
|
size="x-small"
|
|||
|
|
class="ml-1 oauth-status-chip"
|
|||
|
|
>
|
|||
|
|
{{ app.isActive ? 'فعال' : 'غیرفعال' }}
|
|||
|
|
</v-chip>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="mb-1">
|
|||
|
|
<strong>تایید:</strong>
|
|||
|
|
<v-chip
|
|||
|
|
:color="app.isVerified ? 'success' : 'warning'"
|
|||
|
|
size="x-small"
|
|||
|
|
class="ml-1 oauth-status-chip"
|
|||
|
|
>
|
|||
|
|
{{ app.isVerified ? 'تایید شده' : 'در انتظار تایید' }}
|
|||
|
|
</v-chip>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</v-card-text>
|
|||
|
|
</v-card>
|
|||
|
|
</v-col>
|
|||
|
|
</v-row>
|
|||
|
|
</v-card-text>
|
|||
|
|
</v-card>
|
|||
|
|
|
|||
|
|
<!-- Dialog ایجاد/ویرایش برنامه -->
|
|||
|
|
<v-dialog v-model="showCreateDialog" max-width="700px" persistent class="oauth-dialog">
|
|||
|
|
<v-card>
|
|||
|
|
<v-card-title class="text-h6 pa-6 pb-4">
|
|||
|
|
<v-icon start class="mr-2" color="primary">mdi-oauth</v-icon>
|
|||
|
|
{{ editingApp ? 'ویرایش برنامه OAuth' : 'ایجاد برنامه OAuth جدید' }}
|
|||
|
|
</v-card-title>
|
|||
|
|
|
|||
|
|
<v-card-text class="pa-6 pt-0">
|
|||
|
|
<v-form ref="form" @submit.prevent="saveApplication">
|
|||
|
|
<v-row>
|
|||
|
|
<v-col cols="12" class="oauth-form-field">
|
|||
|
|
<v-text-field
|
|||
|
|
v-model="form.name"
|
|||
|
|
label="نام برنامه *"
|
|||
|
|
:rules="formRules.name"
|
|||
|
|
variant="outlined"
|
|||
|
|
density="comfortable"
|
|||
|
|
prepend-inner-icon="mdi-application"
|
|||
|
|
required
|
|||
|
|
></v-text-field>
|
|||
|
|
</v-col>
|
|||
|
|
|
|||
|
|
<v-col cols="12" class="oauth-form-field">
|
|||
|
|
<v-textarea
|
|||
|
|
v-model="form.description"
|
|||
|
|
label="توضیحات"
|
|||
|
|
variant="outlined"
|
|||
|
|
density="comfortable"
|
|||
|
|
prepend-inner-icon="mdi-text"
|
|||
|
|
rows="3"
|
|||
|
|
auto-grow
|
|||
|
|
></v-textarea>
|
|||
|
|
</v-col>
|
|||
|
|
|
|||
|
|
<v-col cols="12" md="6" class="oauth-form-field">
|
|||
|
|
<v-text-field
|
|||
|
|
v-model="form.website"
|
|||
|
|
label="آدرس وبسایت"
|
|||
|
|
:rules="formRules.website"
|
|||
|
|
variant="outlined"
|
|||
|
|
density="comfortable"
|
|||
|
|
prepend-inner-icon="mdi-web"
|
|||
|
|
type="url"
|
|||
|
|
placeholder="https://example.com"
|
|||
|
|
></v-text-field>
|
|||
|
|
</v-col>
|
|||
|
|
|
|||
|
|
<v-col cols="12" md="6" class="oauth-form-field">
|
|||
|
|
<v-text-field
|
|||
|
|
v-model="form.redirectUri"
|
|||
|
|
label="آدرس بازگشت (Redirect URI) *"
|
|||
|
|
:rules="formRules.redirectUri"
|
|||
|
|
variant="outlined"
|
|||
|
|
density="comfortable"
|
|||
|
|
prepend-inner-icon="mdi-link"
|
|||
|
|
type="url"
|
|||
|
|
placeholder="https://example.com/callback"
|
|||
|
|
required
|
|||
|
|
></v-text-field>
|
|||
|
|
</v-col>
|
|||
|
|
|
|||
|
|
<v-col cols="12" md="6" class="oauth-form-field">
|
|||
|
|
<v-text-field
|
|||
|
|
v-model.number="form.rateLimit"
|
|||
|
|
label="محدودیت درخواست (در ساعت)"
|
|||
|
|
:rules="formRules.rateLimit"
|
|||
|
|
variant="outlined"
|
|||
|
|
density="comfortable"
|
|||
|
|
prepend-inner-icon="mdi-speedometer"
|
|||
|
|
type="number"
|
|||
|
|
min="1"
|
|||
|
|
max="10000"
|
|||
|
|
hint="تعداد درخواست مجاز در هر ساعت"
|
|||
|
|
persistent-hint
|
|||
|
|
></v-text-field>
|
|||
|
|
</v-col>
|
|||
|
|
</v-row>
|
|||
|
|
</v-form>
|
|||
|
|
</v-card-text>
|
|||
|
|
|
|||
|
|
<v-divider></v-divider>
|
|||
|
|
|
|||
|
|
<v-card-actions class="pa-6">
|
|||
|
|
<v-spacer></v-spacer>
|
|||
|
|
<v-btn
|
|||
|
|
variant="outlined"
|
|||
|
|
@click="cancelEdit"
|
|||
|
|
class="mr-2"
|
|||
|
|
>
|
|||
|
|
انصراف
|
|||
|
|
</v-btn>
|
|||
|
|
<v-btn
|
|||
|
|
color="primary"
|
|||
|
|
@click="saveApplication"
|
|||
|
|
:loading="saving"
|
|||
|
|
prepend-icon="mdi-content-save"
|
|||
|
|
>
|
|||
|
|
{{ editingApp ? 'ویرایش' : 'ایجاد' }}
|
|||
|
|
</v-btn>
|
|||
|
|
</v-card-actions>
|
|||
|
|
</v-card>
|
|||
|
|
</v-dialog>
|
|||
|
|
|
|||
|
|
<!-- Snackbar برای نمایش پیامها -->
|
|||
|
|
<v-snackbar
|
|||
|
|
v-model="snackbar.show"
|
|||
|
|
:color="snackbar.color"
|
|||
|
|
:timeout="snackbar.timeout"
|
|||
|
|
location="top"
|
|||
|
|
>
|
|||
|
|
{{ snackbar.text }}
|
|||
|
|
|
|||
|
|
<template v-slot:actions>
|
|||
|
|
<v-btn
|
|||
|
|
color="white"
|
|||
|
|
variant="text"
|
|||
|
|
@click="snackbar.show = false"
|
|||
|
|
>
|
|||
|
|
بستن
|
|||
|
|
</v-btn>
|
|||
|
|
</template>
|
|||
|
|
</v-snackbar>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script lang="ts">
|
|||
|
|
import { defineComponent, ref, onMounted } from 'vue'
|
|||
|
|
import axios from 'axios'
|
|||
|
|
import Swal from 'sweetalert2'
|
|||
|
|
|
|||
|
|
export default defineComponent({
|
|||
|
|
name: 'OAuthManager',
|
|||
|
|
setup() {
|
|||
|
|
const applications = ref([])
|
|||
|
|
const loading = ref(false)
|
|||
|
|
const saving = ref(false)
|
|||
|
|
const showCreateDialog = ref(false)
|
|||
|
|
const editingApp = ref(null)
|
|||
|
|
|
|||
|
|
const form = ref({
|
|||
|
|
name: '',
|
|||
|
|
description: '',
|
|||
|
|
website: '',
|
|||
|
|
redirectUri: '',
|
|||
|
|
allowedScopes: [],
|
|||
|
|
rateLimit: 1000
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const formRules = {
|
|||
|
|
name: [
|
|||
|
|
(v: any) => !!v || 'نام برنامه الزامی است',
|
|||
|
|
(v: any) => v.length >= 3 || 'نام برنامه باید حداقل 3 کاراکتر باشد',
|
|||
|
|
(v: any) => v.length <= 255 || 'نام برنامه نمیتواند بیشتر از 255 کاراکتر باشد'
|
|||
|
|
],
|
|||
|
|
redirectUri: [
|
|||
|
|
(v: any) => !!v || 'آدرس بازگشت الزامی است',
|
|||
|
|
(v: any) => /^https?:\/\/.+/.test(v) || 'آدرس بازگشت باید یک URL معتبر باشد'
|
|||
|
|
],
|
|||
|
|
website: [
|
|||
|
|
(v: any) => !v || /^https?:\/\/.+/.test(v) || 'آدرس وبسایت باید یک URL معتبر باشد'
|
|||
|
|
],
|
|||
|
|
rateLimit: [
|
|||
|
|
(v: any) => v >= 1 || 'محدودیت درخواست باید حداقل 1 باشد',
|
|||
|
|
(v: any) => v <= 10000 || 'محدودیت درخواست نمیتواند بیشتر از 10000 باشد'
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const snackbar = ref({
|
|||
|
|
show: false,
|
|||
|
|
text: '',
|
|||
|
|
color: 'success',
|
|||
|
|
timeout: 3000
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const loadApplications = async () => {
|
|||
|
|
loading.value = true
|
|||
|
|
try {
|
|||
|
|
const response = await axios.get('/api/oauth/applications')
|
|||
|
|
applications.value = response.data.data || []
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('خطا در بارگذاری برنامهها:', error)
|
|||
|
|
showSnackbar('خطا در بارگذاری برنامهها', 'error')
|
|||
|
|
} finally {
|
|||
|
|
loading.value = false
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const saveApplication = async () => {
|
|||
|
|
const { valid } = await form.value.$refs?.validate()
|
|||
|
|
if (!valid) {
|
|||
|
|
showSnackbar('لطفاً خطاهای فرم را برطرف کنید', 'error')
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
saving.value = true
|
|||
|
|
try {
|
|||
|
|
if (editingApp.value) {
|
|||
|
|
await axios.put(`/api/oauth/applications/${editingApp.value.id}`, form.value)
|
|||
|
|
showSnackbar('برنامه با موفقیت ویرایش شد', 'success')
|
|||
|
|
} else {
|
|||
|
|
const response = await axios.post('/api/oauth/applications', form.value)
|
|||
|
|
showSnackbar('برنامه با موفقیت ایجاد شد', 'success')
|
|||
|
|
|
|||
|
|
// نمایش client_id و client_secret
|
|||
|
|
if (response.data.data?.client_id) {
|
|||
|
|
Swal.fire({
|
|||
|
|
title: 'اطلاعات برنامه',
|
|||
|
|
html: `
|
|||
|
|
<div class="text-right">
|
|||
|
|
<p><strong>Client ID:</strong> <code>${response.data.data.client_id}</code></p>
|
|||
|
|
<p><strong>Client Secret:</strong> <code>${response.data.data.client_secret}</code></p>
|
|||
|
|
<p class="text-warning">⚠️ این اطلاعات را در جای امنی ذخیره کنید!</p>
|
|||
|
|
</div>
|
|||
|
|
`,
|
|||
|
|
icon: 'info'
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
await loadApplications()
|
|||
|
|
cancelEdit()
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('خطا در ذخیره برنامه:', error)
|
|||
|
|
const errorMessage = error.response?.data?.message || 'خطا در ذخیره برنامه'
|
|||
|
|
showSnackbar(errorMessage, 'error')
|
|||
|
|
} finally {
|
|||
|
|
saving.value = false
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const editApplication = (app: any) => {
|
|||
|
|
editingApp.value = app
|
|||
|
|
form.value = {
|
|||
|
|
name: app.name,
|
|||
|
|
description: app.description || '',
|
|||
|
|
website: app.website || '',
|
|||
|
|
redirectUri: app.redirectUri,
|
|||
|
|
allowedScopes: app.allowedScopes || [],
|
|||
|
|
rateLimit: app.rateLimit || 1000
|
|||
|
|
}
|
|||
|
|
showCreateDialog.value = true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const cancelEdit = () => {
|
|||
|
|
editingApp.value = null
|
|||
|
|
showCreateDialog.value = false
|
|||
|
|
form.value = {
|
|||
|
|
name: '',
|
|||
|
|
description: '',
|
|||
|
|
website: '',
|
|||
|
|
redirectUri: '',
|
|||
|
|
allowedScopes: [],
|
|||
|
|
rateLimit: 1000
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const deleteApplication = async (app: any) => {
|
|||
|
|
const result = await Swal.fire({
|
|||
|
|
title: 'حذف برنامه',
|
|||
|
|
text: `آیا از حذف برنامه "${app.name}" اطمینان دارید؟`,
|
|||
|
|
icon: 'warning',
|
|||
|
|
showCancelButton: true,
|
|||
|
|
confirmButtonText: 'حذف',
|
|||
|
|
cancelButtonText: 'انصراف'
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if (result.isConfirmed) {
|
|||
|
|
try {
|
|||
|
|
await axios.delete(`/api/oauth/applications/${app.id}`)
|
|||
|
|
showSnackbar('برنامه با موفقیت حذف شد', 'success')
|
|||
|
|
await loadApplications()
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('خطا در حذف برنامه:', error)
|
|||
|
|
const errorMessage = error.response?.data?.message || 'خطا در حذف برنامه'
|
|||
|
|
showSnackbar(errorMessage, 'error')
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const regenerateSecret = async (app: any) => {
|
|||
|
|
const result = await Swal.fire({
|
|||
|
|
title: 'بازسازی کلید',
|
|||
|
|
text: 'آیا از بازسازی Client Secret اطمینان دارید؟ تمام توکنهای موجود لغو خواهند شد.',
|
|||
|
|
icon: 'warning',
|
|||
|
|
showCancelButton: true,
|
|||
|
|
confirmButtonText: 'بازسازی',
|
|||
|
|
cancelButtonText: 'انصراف'
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if (result.isConfirmed) {
|
|||
|
|
try {
|
|||
|
|
const response = await axios.post(`/api/oauth/applications/${app.id}/regenerate-secret`)
|
|||
|
|
Swal.fire({
|
|||
|
|
title: 'کلید جدید',
|
|||
|
|
html: `<p><strong>Client Secret جدید:</strong> <code>${response.data.data.client_secret}</code></p>`,
|
|||
|
|
icon: 'success'
|
|||
|
|
})
|
|||
|
|
await loadApplications()
|
|||
|
|
showSnackbar('کلید جدید با موفقیت ایجاد شد', 'success')
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('خطا در بازسازی کلید:', error)
|
|||
|
|
const errorMessage = error.response?.data?.message || 'خطا در بازسازی کلید'
|
|||
|
|
showSnackbar(errorMessage, 'error')
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const revokeTokens = async (app: any) => {
|
|||
|
|
const result = await Swal.fire({
|
|||
|
|
title: 'لغو توکنها',
|
|||
|
|
text: 'آیا از لغو تمام توکنهای این برنامه اطمینان دارید؟',
|
|||
|
|
icon: 'warning',
|
|||
|
|
showCancelButton: true,
|
|||
|
|
confirmButtonText: 'لغو توکنها',
|
|||
|
|
cancelButtonText: 'انصراف'
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if (result.isConfirmed) {
|
|||
|
|
try {
|
|||
|
|
const response = await axios.post(`/api/oauth/applications/${app.id}/revoke-tokens`)
|
|||
|
|
showSnackbar(`${response.data.data.revokedCount} توکن لغو شد`, 'success')
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('خطا در لغو توکنها:', error)
|
|||
|
|
const errorMessage = error.response?.data?.message || 'خطا در لغو توکنها'
|
|||
|
|
showSnackbar(errorMessage, 'error')
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const showStats = async (app: any) => {
|
|||
|
|
try {
|
|||
|
|
const response = await axios.get(`/api/oauth/applications/${app.id}/stats`)
|
|||
|
|
const stats = response.data.data
|
|||
|
|
|
|||
|
|
Swal.fire({
|
|||
|
|
title: `آمار استفاده - ${app.name}`,
|
|||
|
|
html: `
|
|||
|
|
<div class="text-right">
|
|||
|
|
<p><strong>کل توکنها:</strong> ${stats.totalTokens}</p>
|
|||
|
|
<p><strong>توکنهای فعال:</strong> ${stats.activeTokens}</p>
|
|||
|
|
<p><strong>توکنهای منقضی شده:</strong> ${stats.expiredTokens}</p>
|
|||
|
|
${stats.lastUsed ? `<p><strong>آخرین استفاده:</strong> ${new Date(stats.lastUsed).toLocaleDateString('fa-IR')}</p>` : ''}
|
|||
|
|
</div>
|
|||
|
|
`,
|
|||
|
|
icon: 'info'
|
|||
|
|
})
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('خطا در بارگذاری آمار:', error)
|
|||
|
|
showSnackbar('خطا در بارگذاری آمار', 'error')
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const copyToClipboard = async (text: string) => {
|
|||
|
|
try {
|
|||
|
|
await navigator.clipboard.writeText(text)
|
|||
|
|
showSnackbar('متن در کلیپبورد کپی شد', 'success')
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('خطا در کپی:', error)
|
|||
|
|
showSnackbar('خطا در کپی کردن متن', 'error')
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const showSnackbar = (text: string, color = 'success') => {
|
|||
|
|
snackbar.value = {
|
|||
|
|
show: true,
|
|||
|
|
text: text,
|
|||
|
|
color: color,
|
|||
|
|
timeout: 3000
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
onMounted(() => {
|
|||
|
|
loadApplications()
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
applications,
|
|||
|
|
loading,
|
|||
|
|
saving,
|
|||
|
|
showCreateDialog,
|
|||
|
|
editingApp,
|
|||
|
|
form,
|
|||
|
|
formRules,
|
|||
|
|
snackbar,
|
|||
|
|
saveApplication,
|
|||
|
|
editApplication,
|
|||
|
|
cancelEdit,
|
|||
|
|
deleteApplication,
|
|||
|
|
regenerateSecret,
|
|||
|
|
revokeTokens,
|
|||
|
|
showStats,
|
|||
|
|
copyToClipboard
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style scoped>
|
|||
|
|
@import './oauth-styles.css';
|
|||
|
|
</style>
|