First working version
Some checks failed
CI / build (20.x) (push) Has been cancelled
CI / build (22.x) (push) Has been cancelled

This commit is contained in:
2025-06-21 01:01:55 -05:00
parent d961cbb51d
commit 5ab210f2bf
7 changed files with 1232 additions and 15 deletions

View File

@ -6,15 +6,231 @@ const expand = text => {
.replace(/\*(.+?)\*/g, '<i>$1</i>')
}
const error = text => {
return `<div class="error" style="color:#F88; margin: 10px 0;">${text}</div>`
}
const success = text => {
return `<div class="success" style="color:#4A4; margin: 10px 0;">${text}</div>`
}
const tokenForm = () => {
return `
<div class=fields>
<input type=text name=tokenName size=30 placeholder="token name" required>
<select name=expiresInDays>
<option value="">never expires</option>
<option value="7">7 days</option>
<option value="30">30 days</option>
<option value="90">90 days</option>
<option value="365">1 year</option>
</select>
</div>
<p><button class=create-token>Create Token</button></p>
<span class=create-result></span>
<hr>
`
}
const tokenList = (tokens) => {
if (!tokens || tokens.length === 0) {
return '<p>No tokens created yet.</p>'
}
const tokenRows = tokens.map(token => {
const status = token.revoked ? 'revoked' :
(token.expires && new Date(token.expires) < new Date()) ? 'expired' :
'active'
const lastUsed = token.lastUsed ? new Date(token.lastUsed).toLocaleDateString() : 'never'
const expires = token.expires ? new Date(token.expires).toLocaleDateString() : 'never'
return `
<p><strong>${expand(token.name)}</strong> (...${token.displayHint}) - ${status}
${!token.revoked ? `<button class=revoke-token data-token-name="${token.name}">revoke</button>` : ''}
<button class=delete-token data-token-name="${token.name}">delete</button>
<br><small>created: ${new Date(token.created).toLocaleDateString()}, expires: ${expires}, last used: ${lastUsed}</small>
</p>
`
}).join('')
return `
<div class=token-list>
${tokenRows}
<span class=list-result></span>
</div>
`
}
const emit = ($item, item) => {
return $item.append(`
<p style="background-color:#eee;padding:15px;">
${expand(item.text)}
</p>`)
const content = `
<div style="background-color:#eee; padding:15px;">
<center>
<p><img src='/favicon.png' width=16> <span style='color:gray;'>${window.location.host}</span></p>
<p>${expand(item.text)}</p>
${tokenForm()}
</center>
<div class=tokens-container>
<p>Loading tokens...</p>
</div>
<center>
<p><button class=refresh-tokens>Refresh Tokens</button></p>
</center>
</div>
`
$item.html(content)
// Load tokens on initial render
loadTokens($item)
}
const loadTokens = ($item) => {
fetch('/plugin/useraccesstokens/list')
.then(res => {
if (!res.ok) {
throw new Error(`${res.status} ${res.statusText}`)
}
return res.json()
})
.then(tokens => {
$item.find('.tokens-container').html(tokenList(tokens))
})
.catch(err => {
$item.find('.tokens-container').html(error(`Failed to load tokens: ${err.message}`))
})
}
const createToken = ($item) => {
const $form = $item.find('.fields')
const name = $form.find('input[name="tokenName"]').val().trim()
const expiresInDays = $form.find('select[name="expiresInDays"]').val()
$item.find('.error, .success').remove()
if (!name) {
$item.find('.create-result').html(error('Token name is required'))
return
}
const data = { name }
if (expiresInDays) {
data.expiresInDays = parseInt(expiresInDays)
}
fetch('/plugin/useraccesstokens/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
})
.then(res => {
if (!res.ok) {
return res.json().then(err => Promise.reject(err))
}
return res.json()
})
.then(result => {
$item.find('.create-result').html(`
${success('Token created successfully!')}
<p><strong>Save this token now - you won't see it again:</strong><br>
<code>${result.token}</code></p>
`)
// Clear form
$form.find('input[name="tokenName"]').val('')
$form.find('select[name="expiresInDays"]').val('')
// Refresh token list
loadTokens($item)
})
.catch(err => {
$item.find('.create-result').html(error(err.error || 'Failed to create token'))
})
}
const revokeToken = ($item, tokenName) => {
if (!confirm(`Are you sure you want to revoke the token "${tokenName}"?`)) {
return
}
fetch('/plugin/useraccesstokens/revoke', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name: tokenName })
})
.then(res => {
if (!res.ok) {
return res.json().then(err => Promise.reject(err))
}
return res.json()
})
.then(result => {
$item.find('.list-result').html(success(result.message))
loadTokens($item)
})
.catch(err => {
$item.find('.list-result').html(error(err.error || 'Failed to revoke token'))
})
}
const deleteToken = ($item, tokenName) => {
if (!confirm(`Are you sure you want to permanently delete the token "${tokenName}"? This cannot be undone.`)) {
return
}
fetch('/plugin/useraccesstokens/delete', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name: tokenName })
})
.then(res => {
if (!res.ok) {
return res.json().then(err => Promise.reject(err))
}
return res.json()
})
.then(result => {
$item.find('.list-result').html(success(result.message))
loadTokens($item)
})
.catch(err => {
$item.find('.list-result').html(error(err.error || 'Failed to delete token'))
})
}
const bind = ($item, item) => {
return $item.dblclick(() => {
// Handle create token button
$item.on('click', '.create-token', () => {
createToken($item)
})
// Handle refresh tokens button
$item.on('click', '.refresh-tokens', () => {
loadTokens($item)
})
// Handle revoke token buttons
$item.on('click', '.revoke-token', (e) => {
const tokenName = $(e.target).data('token-name')
revokeToken($item, tokenName)
})
// Handle delete token buttons
$item.on('click', '.delete-token', (e) => {
const tokenName = $(e.target).data('token-name')
deleteToken($item, tokenName)
})
// Handle double-click for text editing
$item.dblclick(() => {
return wiki.textEditor($item, item)
})
}