First working version
This commit is contained in:
@ -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)
|
||||
})
|
||||
}
|
||||
|
Reference in New Issue
Block a user