You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
OneAuth/ui/page/sys/oauth/providers.html

270 lines
7.0 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta name="description" content="Identity Providers">
<title>{{ $t('nav.oauth_providers') }}</title>
<style>
body {
display: flex;
flex-direction: column;
gap: var(--spacing-lg);
padding: var(--spacing-lg);
box-sizing: border-box;
background-color: var(--bg-color);
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: var(--spacing-md);
border-bottom: 1px solid var(--border-color);
}
.page-title {
font-size: var(--font-size-2xl);
font-weight: bold;
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: var(--spacing-lg);
}
.card {
background: var(--bg-color-secondary);
padding: var(--spacing-lg);
border-radius: var(--radius-lg);
border: 1px solid var(--border-color);
display: flex;
flex-direction: column;
gap: var(--spacing-md);
transition: all 0.2s;
}
.card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
border-color: var(--color-primary);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.card-title {
font-weight: bold;
font-size: var(--font-size-lg);
display: flex;
align-items: center;
gap: 8px;
}
.status-badge {
padding: 2px 8px;
border-radius: 12px;
font-size: 12px;
}
.status-enabled {
background: #e6f4ea;
color: #1e8e3e;
}
.status-disabled {
background: #fce8e6;
color: #c5221f;
}
.info-row {
display: flex;
flex-direction: column;
gap: 4px;
}
.info-label {
font-size: 12px;
color: var(--text-color-tertiary);
}
.info-value {
font-family: monospace;
background: var(--bg-color-tertiary);
padding: 4px;
border-radius: 4px;
word-break: break-all;
font-size: 13px;
}
.actions {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: auto;
padding-top: 12px;
border-top: 1px solid var(--border-color);
}
</style>
</head>
<body>
<div class="page-header">
<div class="page-title">
<i class="fas fa-id-card" style="color: var(--color-primary);"></i>
{{ $t('nav.oauth_providers') }}
</div>
<v-btn color="primary" :click="openCreateModal">
<i class="fas fa-plus"></i>
{{ $t('oauth.provider.create') }}
</v-btn>
</div>
<div class="grid">
<div class="card" v-for="p in providers">
<div class="card-header">
<div class="card-title">
<i :class="'fab fa-' + p.icon" v-if="p.icon"></i>
{{ p.name }}
</div>
<span class="status-badge" :class="p.enabled ? 'status-enabled' : 'status-disabled'">
{{ p.enabled ? $t('oauth.provider.enabled') : 'Disabled' }}
</span>
</div>
<div class="info-row">
<span class="info-label">{{ $t('oauth.provider.code') }}</span>
<div class="info-value">{{ p.code }}</div>
</div>
<div class="info-row">
<span class="info-label">{{ $t('oauth.provider.client_id') }}</span>
<div class="info-value">{{ p.client_id || '-' }}</div>
</div>
<div class="info-row">
<span class="info-label">{{ $t('oauth.provider.redirect_uri') }}</span>
<div class="info-value" @click="copy(p.code)" style="cursor: pointer;" title="Click to copy">
{{ getCallbackUrl(p.code) }}
</div>
</div>
<div class="actions">
<v-btn icon size="sm" variant="outline" :click="() => openEditModal(p)">
<i class="fas fa-edit"></i>
</v-btn>
<v-btn icon size="sm" color="danger" variant="outline" :click="() => deleteProvider(p)">
<i class="fas fa-trash"></i>
</v-btn>
</div>
</div>
</div>
<v-dialog v:visible="showModal" :title="isEdit ? $t('oauth.provider.edit') : $t('oauth.provider.create')">
<form @submit.prevent="saveProvider" style="display: grid; gap: 16px;">
<v-input :label="$t('oauth.provider.code')" required v:value="formData.code" :disabled="isEdit"></v-input>
<v-input :label="$t('oauth.provider.name')" required v:value="formData.name"></v-input>
<v-input :label="$t('oauth.provider.client_id')" required v:value="formData.client_id"></v-input>
<v-input :label="$t('oauth.provider.client_secret')" type="password" v:value="formData.client_secret"
:placeholder="isEdit ? 'Leave empty to keep unchanged' : ''"></v-input>
<div style="display: flex; align-items: center; gap: 8px;">
<input type="checkbox" id="enabled" v:checked="formData.enabled"
@change="e => formData.enabled = e.target.checked">
<label for="enabled">{{ $t('oauth.provider.enabled') }}</label>
</div>
</form>
<div vslot="footer">
<v-btn variant="outline" :click="closeModal">{{ $t('common.cancel') }}</v-btn>
<v-btn color="primary" :click="saveProvider">{{ $t('common.save') }}</v-btn>
</div>
</v-dialog>
</body>
<script setup>
providers = [];
showModal = false;
isEdit = false;
formData = {
code: "",
name: "",
client_id: "",
client_secret: "",
enabled: false
};
loadProviders = async () => {
try {
const res = await $env.$vbase.request('GET', '/api/oauth/providers');
providers = res.items || [];
} catch (e) {
$message.error(e.message);
}
}
getCallbackUrl = (code) => {
return `${window.location.origin}/callback/${code}`;
}
copy = (code) => {
const url = getCallbackUrl(code);
navigator.clipboard.writeText(url).then(() => {
$message.success('Copied!');
});
};
openCreateModal = () => {
isEdit = false;
formData = { code: "", name: "", client_id: "", client_secret: "", enabled: true };
showModal = true;
};
openEditModal = async (p) => {
isEdit = true;
try {
const detail = await $axios.get(`/api/oauth/providers/${p.code}`);
formData = { ...detail, client_secret: "" };
showModal = true;
} catch (e) {
$message.error(e.message);
}
};
closeModal = () => showModal = false;
saveProvider = async () => {
try {
if (isEdit) {
await $axios.patch(`/api/oauth/providers/${formData.code}`, formData);
} else {
await $axios.post('/api/oauth/providers', formData);
}
$message.success("Saved");
closeModal();
loadProviders();
} catch (e) {
$message.error(e.message);
}
};
deleteProvider = async (p) => {
if (!confirm(`Delete ${p.name}?`)) return;
try {
await $axios.delete(`/api/oauth/providers/${p.code}`);
$message.success("Deleted");
loadProviders();
} catch (e) {
$message.error(e.message);
}
};
</script>
<script>
$data.loadProviders();
</script>
</html>