ux: token Proxmox en deux champs séparés (ID + Secret)

Install.vue et Settings.vue : remplace le champ unique "PVEAPIToken=..."
par deux inputs distincts — Token ID (ex: enzo@pam!panel) et Secret
(uuid). L'assemblage PVEAPIToken=ID=Secret se fait côté frontend avant
envoi. Plus besoin de connaître le format interne.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
enzo 2026-03-21 00:24:57 +01:00
parent d55ecdcd97
commit 233f690214
4 changed files with 48 additions and 20 deletions

View file

@ -33,8 +33,9 @@
"sshSuccess": "SSH connection successful!", "sshSuccess": "SSH connection successful!",
"sshFailed": "SSH connection failed", "sshFailed": "SSH connection failed",
"proxmoxUrl": "Proxmox URL", "proxmoxUrl": "Proxmox URL",
"proxmoxToken": "Proxmox API token", "proxmoxTokenId": "Token ID",
"proxmoxTokenHint": "Format: PVEAPIToken=user{'@'}realm!tokenid=secret", "proxmoxTokenSecret": "Token secret",
"proxmoxTokenHint": "Token ID: enzo{'@'}pam!panel — Secret: UUID generated by Proxmox",
"back": "Back", "back": "Back",
"next": "Next", "next": "Next",
"finish": "Complete installation", "finish": "Complete installation",
@ -115,7 +116,9 @@
"sshUsername": "SSH username", "sshUsername": "SSH username",
"sshPassword": "SSH password", "sshPassword": "SSH password",
"proxmoxUrl": "Proxmox URL", "proxmoxUrl": "Proxmox URL",
"proxmoxToken": "Proxmox API token", "proxmoxTokenId": "Proxmox Token ID",
"proxmoxTokenIdPlaceholder": "enzo@pam!panel",
"proxmoxTokenSecret": "Token secret",
"secretPlaceholder": "Leave empty to keep current value", "secretPlaceholder": "Leave empty to keep current value",
"darkMode": "Dark mode", "darkMode": "Dark mode",
"sidebarPosition": "Sidebar position", "sidebarPosition": "Sidebar position",

View file

@ -33,8 +33,9 @@
"sshSuccess": "Connexion SSH réussie !", "sshSuccess": "Connexion SSH réussie !",
"sshFailed": "Connexion SSH échouée", "sshFailed": "Connexion SSH échouée",
"proxmoxUrl": "URL Proxmox", "proxmoxUrl": "URL Proxmox",
"proxmoxToken": "Token API Proxmox", "proxmoxTokenId": "Token ID",
"proxmoxTokenHint": "Format : PVEAPIToken=user{'@'}realm!tokenid=secret", "proxmoxTokenSecret": "Secret du token",
"proxmoxTokenHint": "Token ID : enzo{'@'}pam!panel — Secret : uuid généré par Proxmox",
"back": "Retour", "back": "Retour",
"next": "Suivant", "next": "Suivant",
"finish": "Terminer l'installation", "finish": "Terminer l'installation",
@ -115,7 +116,9 @@
"sshUsername": "Utilisateur SSH", "sshUsername": "Utilisateur SSH",
"sshPassword": "Mot de passe SSH", "sshPassword": "Mot de passe SSH",
"proxmoxUrl": "URL Proxmox", "proxmoxUrl": "URL Proxmox",
"proxmoxToken": "Token API Proxmox", "proxmoxTokenId": "Token ID Proxmox",
"proxmoxTokenIdPlaceholder": "enzo@pam!panel",
"proxmoxTokenSecret": "Secret du token",
"secretPlaceholder": "Laisser vide pour ne pas modifier", "secretPlaceholder": "Laisser vide pour ne pas modifier",
"darkMode": "Mode sombre", "darkMode": "Mode sombre",
"sidebarPosition": "Position de la sidebar", "sidebarPosition": "Position de la sidebar",

View file

@ -98,8 +98,12 @@
<input v-model="form.proxmoxUrl" class="neu-input" placeholder="https://10.0.0.1:8006" /> <input v-model="form.proxmoxUrl" class="neu-input" placeholder="https://10.0.0.1:8006" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label>{{ t('install.proxmoxToken') }}</label> <label>{{ t('install.proxmoxTokenId') }}</label>
<input v-model="form.proxmoxToken" class="neu-input" placeholder="PVEAPIToken=enzo@pam!panel=xxxx" /> <input v-model="form.proxmoxTokenId" class="neu-input" placeholder="enzo@pam!panel" />
</div>
<div class="form-group">
<label>{{ t('install.proxmoxTokenSecret') }}</label>
<input v-model="form.proxmoxTokenSecret" type="password" class="neu-input" placeholder="ed57ea62-cadc-4ddd-..." />
<small>{{ t('install.proxmoxTokenHint') }}</small> <small>{{ t('install.proxmoxTokenHint') }}</small>
</div> </div>
</div> </div>
@ -187,7 +191,8 @@ const form = ref({
sshUsername: 'enzo', sshUsername: 'enzo',
sshPassword: '', sshPassword: '',
proxmoxUrl: 'https://10.0.0.1:8006', proxmoxUrl: 'https://10.0.0.1:8006',
proxmoxToken: '', proxmoxTokenId: '',
proxmoxTokenSecret: '',
}) })
const canProceed = computed(() => { const canProceed = computed(() => {
@ -264,7 +269,9 @@ async function finalize() {
ssh_username: form.value.sshUsername, ssh_username: form.value.sshUsername,
ssh_password: form.value.sshPassword, ssh_password: form.value.sshPassword,
proxmox_url: form.value.proxmoxUrl, proxmox_url: form.value.proxmoxUrl,
proxmox_token: form.value.proxmoxToken, proxmox_token: (form.value.proxmoxTokenId && form.value.proxmoxTokenSecret)
? `PVEAPIToken=${form.value.proxmoxTokenId}=${form.value.proxmoxTokenSecret}`
: '',
}), }),
}) })

View file

@ -64,8 +64,12 @@
<input v-model="settings.proxmox_url" class="neu-input" placeholder="https://10.0.0.1:8006" /> <input v-model="settings.proxmox_url" class="neu-input" placeholder="https://10.0.0.1:8006" />
</div> </div>
<div class="form-row"> <div class="form-row">
<label>{{ t('settings.proxmoxToken') }}</label> <label>{{ t('settings.proxmoxTokenId') }}</label>
<input v-model="secrets.proxmox_token" type="password" class="neu-input" :placeholder="t('settings.secretPlaceholder')" autocomplete="new-password" /> <input v-model="secrets.proxmox_token_id" class="neu-input" :placeholder="t('settings.proxmoxTokenIdPlaceholder')" autocomplete="off" />
</div>
<div class="form-row">
<label>{{ t('settings.proxmoxTokenSecret') }}</label>
<input v-model="secrets.proxmox_token_secret" type="password" class="neu-input" :placeholder="t('settings.secretPlaceholder')" autocomplete="new-password" />
</div> </div>
</div> </div>
</div> </div>
@ -184,7 +188,8 @@ const settings = ref({
// Champs sensibles write-only, jamais retournés par l'API // Champs sensibles write-only, jamais retournés par l'API
const secrets = ref({ const secrets = ref({
ssh_password: '', ssh_password: '',
proxmox_token: '', proxmox_token_id: '',
proxmox_token_secret: '',
}) })
onMounted(async () => { onMounted(async () => {
@ -225,13 +230,22 @@ async function saveSettings() {
saving.value = true saving.value = true
saveSuccess.value = false saveSuccess.value = false
const allEntries: [string, string][] = [ const entries: [string, string][] = [...Object.entries(settings.value)]
...Object.entries(settings.value),
// Secrets : envoyés seulement si non-vides (le backend ignore les valeurs vides)
...Object.entries(secrets.value).filter(([, v]) => v !== ''),
]
for (const [key, value] of allEntries) { // Mot de passe SSH envoyé seulement si non-vide
if (secrets.value.ssh_password) {
entries.push(['ssh_password', secrets.value.ssh_password])
}
// Token Proxmox assemblé si les deux champs sont remplis
if (secrets.value.proxmox_token_id && secrets.value.proxmox_token_secret) {
entries.push([
'proxmox_token',
`PVEAPIToken=${secrets.value.proxmox_token_id}=${secrets.value.proxmox_token_secret}`,
])
}
for (const [key, value] of entries) {
await fetch(`/api/settings/${key}`, { await fetch(`/api/settings/${key}`, {
method: 'PUT', method: 'PUT',
headers: { headers: {
@ -244,7 +258,8 @@ async function saveSettings() {
// Vider les champs secrets après sauvegarde // Vider les champs secrets après sauvegarde
secrets.value.ssh_password = '' secrets.value.ssh_password = ''
secrets.value.proxmox_token = '' secrets.value.proxmox_token_id = ''
secrets.value.proxmox_token_secret = ''
saving.value = false saving.value = false
saveSuccess.value = true saveSuccess.value = true