Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
V
vidai
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
SexHackMe
vidai
Commits
a1b6e302
Commit
a1b6e302
authored
Oct 06, 2025
by
Stefy Lanza (nextime / spora )
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update cluster tokens page to match API tokens flow with modals and copy functionality
parent
b2af681f
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
160 additions
and
47 deletions
+160
-47
cluster_tokens.html
templates/admin/cluster_tokens.html
+155
-45
web.py
vidai/web.py
+5
-2
No files found.
templates/admin/cluster_tokens.html
View file @
a1b6e302
...
...
@@ -26,6 +26,19 @@
.alert-error
{
background
:
#fee2e2
;
color
:
#dc2626
;
border
:
1px
solid
#fecaca
;
}
.alert-success
{
background
:
#d1fae5
;
color
:
#065f46
;
border
:
1px
solid
#a7f3d0
;
}
.token-preview
{
font-family
:
monospace
;
background
:
#f1f5f9
;
padding
:
0.25rem
0.5rem
;
border-radius
:
4px
;
font-size
:
0.875rem
;
}
.form-group
{
margin-bottom
:
1.5rem
;
}
.form-group
label
{
display
:
block
;
margin-bottom
:
0.5rem
;
color
:
#374151
;
font-weight
:
500
;
}
.form-group
input
{
width
:
100%
;
padding
:
0.75rem
;
border
:
2px
solid
#e5e7eb
;
border-radius
:
8px
;
font-size
:
1rem
;
}
.form-group
input
:focus
{
outline
:
none
;
border-color
:
#667eea
;
}
.token-display
{
background
:
#f8fafc
;
padding
:
1rem
;
border-radius
:
8px
;
border
:
1px
solid
#e5e7eb
;
margin
:
1rem
0
;
}
.token-display
pre
{
word-break
:
break-all
;
white-space
:
pre-wrap
;
font-family
:
monospace
;
background
:
white
;
padding
:
1rem
;
border-radius
:
4px
;
border
:
1px
solid
#d1d5db
;
}
.copy-btn
{
margin-top
:
0.5rem
;
padding
:
0.5rem
1rem
;
font-size
:
0.9rem
;
}
.modal
{
display
:
none
;
position
:
fixed
;
z-index
:
1000
;
left
:
0
;
top
:
0
;
width
:
100%
;
height
:
100%
;
background-color
:
rgba
(
0
,
0
,
0
,
0.5
);
}
.modal-content
{
background-color
:
white
;
position
:
fixed
;
top
:
50%
;
left
:
50%
;
transform
:
translate
(
-50%
,
-50%
);
padding
:
2rem
;
width
:
90%
;
max-width
:
500px
;
border-radius
:
12px
;
}
.modal-header
{
margin-bottom
:
1rem
;
position
:
relative
;
}
.modal-body
{
margin-bottom
:
1rem
;
}
.modal-footer
{
text-align
:
right
;
margin-top
:
1.5rem
;
}
.close
{
position
:
absolute
;
top
:
0
;
right
:
0
;
cursor
:
pointer
;
font-size
:
1.5rem
;
line-height
:
1
;
}
</style>
{% endblock %}
...
...
@@ -35,6 +48,7 @@
<div
class=
"card-header"
>
<h3><i
class=
"fas fa-key"
></i>
Cluster Tokens
</h3>
<p>
Manage authentication tokens for worker processes in the cluster.
</p>
<button
onclick=
"openAddTokenModal()"
class=
"btn"
style=
"margin-left: auto;"
><i
class=
"fas fa-plus"
></i>
Add Token
</button>
</div>
{% with messages = get_flashed_messages(with_categories=true) %}
...
...
@@ -45,29 +59,13 @@
{% endif %}
{% endwith %}
<div
class=
"admin-card"
style=
"margin-bottom: 2rem;"
>
<div
class=
"card-header"
>
<h3><i
class=
"fas fa-plus"
></i>
Create New Token
</h3>
</div>
<form
method=
"post"
action=
"/admin/cluster_tokens/generate"
>
<div
style=
"display: flex; gap: 1rem; align-items: end;"
>
<div
style=
"flex: 1;"
>
<label
for=
"token_name"
style=
"display: block; margin-bottom: 0.5rem; color: #374151; font-weight: 500;"
>
Token Name
</label>
<input
type=
"text"
id=
"token_name"
name=
"token_name"
required
style=
"width: 100%; padding: 0.5rem; border: 2px solid #e5e7eb; border-radius: 8px; font-size: 1rem;"
>
</div>
<button
type=
"submit"
class=
"btn"
><i
class=
"fas fa-plus"
></i>
Generate Token
</button>
</div>
</form>
</div>
<table
class=
"table"
>
<thead>
<tr>
<th>
Name
</th>
<th>
Token
</th>
<th>
Status
</th>
<th>
Created
</th>
<th>
Last Used
</th>
<th>
Status
</th>
<th>
Actions
</th>
</tr>
</thead>
...
...
@@ -75,50 +73,162 @@
{% for token in worker_tokens %}
<tr>
<td>
{{ token.get('name') }}
</td>
<td><span
class=
"token-preview"
>
{{ token.get('token')[:20] }}...
</span></td>
<td><span
class=
"status-{{ 'active' if token.get('active') else 'inactive' }}"
>
{{ 'Active' if token.get('active') else 'Inactive' }}
</span></td>
<td>
{{ token.get('created_at', 'N/A')[:19] if token.get('created_at') else 'N/A' }}
</td>
<td>
{{ token.get('last_used', 'Never')[:19] if token.get('last_used') else 'Never' }}
</td>
<td
class=
"actions-cell"
>
<td><span
class=
"status-{{ 'active' if token.get('active') else 'inactive' }}"
>
{{ 'Active' if token.get('active') else 'Inactive' }}
</span></td>
<td>
{% if token.get('active') %}
<form
method=
"post"
action=
"/admin/cluster_tokens/{{ token.get('id') }}/deactivate"
style=
"display: inline;"
>
<button
type=
"submit"
class=
"btn
-icon"
title=
"Deactivate"
><i
class=
"fas fa-ban"
></i>
</button>
<button
type=
"submit"
class=
"btn
"
style=
"padding: 0.5rem 1rem; font-size: 0.9rem; background: #f59e0b;"
>
Deactivate
</button>
</form>
{% else %}
<form
method=
"post"
action=
"/admin/cluster_tokens/{{ token.get('id') }}/activate"
style=
"display: inline;"
>
<button
type=
"submit"
class=
"btn btn-success"
title=
"Activate"
style=
"padding: 0.25rem 0.5rem; font-size: 0.75
rem;"
>
Activate
</button>
<button
type=
"submit"
class=
"btn btn-success"
style=
"padding: 0.5rem 1rem; font-size: 0.9
rem;"
>
Activate
</button>
</form>
{% endif %}
<button
onclick=
'deleteToken({{ token.get("id") }}, "{{ token.get("name") }}")'
class=
"btn
-icon"
title=
"Delete"
style=
"color: #dc2626;"
><i
class=
"fas fa-trash"
></i>
</button>
<button
onclick=
'deleteToken({{ token.get("id") }}, "{{ token.get("name") }}")'
class=
"btn
btn-danger"
style=
"padding: 0.5rem 1rem; font-size: 0.9rem;"
>
Delete
</button>
</td>
</tr>
{% endfor %}
{% if not worker_tokens %}
<tr>
<td
colspan=
"5"
style=
"text-align: center; color: #6b7280;"
>
No cluster tokens found. Generate your first token above.
</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
</div>
{% if not worker_tokens %}
<p
style=
"text-align: center; color: #64748b; margin-top: 2rem;"
>
No cluster tokens found. Generate your first token above.
</p>
{% endif %}
<!-- Add Token Modal -->
<div
id=
"addTokenModal"
class=
"modal"
>
<div
class=
"modal-content"
>
<div
class=
"modal-header"
>
<h3><i
class=
"fas fa-plus"
></i>
Generate New Cluster Token
</h3>
<span
class=
"close"
onclick=
"closeAddTokenModal()"
>
×
</span>
</div>
<form
method=
"post"
action=
"/admin/cluster_tokens/generate"
>
<div
class=
"modal-body"
>
<div
class=
"form-group"
>
<label
for=
"modal_token_name"
>
Token Name
</label>
<input
type=
"text"
id=
"modal_token_name"
name=
"token_name"
placeholder=
"e.g., Worker Node 1"
required
>
</div>
</div>
<div
class=
"modal-footer"
>
<button
type=
"button"
onclick=
"closeAddTokenModal()"
class=
"btn"
style=
"background: #6b7280;"
>
Cancel
</button>
<button
type=
"submit"
class=
"btn"
>
Generate Token
</button>
</div>
</form>
</div>
</div>
<!-- Token Generated Modal -->
{% if generated_token %}
<div
id=
"tokenModal"
class=
"modal"
style=
"display: block;"
>
<div
class=
"modal-content"
>
<div
class=
"modal-header"
>
<h3>
Cluster Token Generated Successfully!
</h3>
<span
class=
"close"
onclick=
"closeTokenModal()"
>
×
</span>
</div>
<div
class=
"modal-body"
>
<p><strong>
Token Name:
</strong>
{{ token_name }}
</p>
<p><strong>
Token:
</strong></p>
<div
class=
"token-display"
style=
"margin: 1rem 0;"
>
<pre
id=
"modalTokenText"
>
{{ generated_token }}
</pre>
<button
class=
"btn copy-btn"
onclick=
"copyTokenFromModal()"
>
Copy Token
</button>
</div>
<p
style=
"color: #dc2626; font-weight: 500;"
>
Copy this token now. You won't be able to see it again!
</p>
</div>
<div
class=
"modal-footer"
>
<button
onclick=
"closeTokenModal()"
class=
"btn"
>
Close
</button>
</div>
</div>
</div>
{% endif %}
<!-- Delete Confirmation Modal -->
<div
id=
"deleteModal"
class=
"modal"
>
<div
class=
"modal-content"
>
<div
class=
"modal-header"
>
<h3>
Delete Cluster Token
</h3>
</div>
<div
class=
"modal-body"
>
<p>
Are you sure you want to delete the token "
<span
id=
"deleteTokenName"
></span>
"?
</p>
<p
style=
"color: #dc2626; font-weight: 500;"
>
This action cannot be undone. Any worker processes using this token will stop working.
</p>
</div>
<div
class=
"modal-footer"
>
<button
onclick=
"closeDeleteModal()"
class=
"btn"
style=
"background: #6b7280;"
>
Cancel
</button>
<form
id=
"deleteForm"
method=
"post"
action=
""
style=
"display: inline;"
>
<button
type=
"submit"
class=
"btn btn-danger"
>
Delete Token
</button>
</form>
</div>
</div>
</div>
<script>
function
deleteToken
(
tokenId
,
tokenName
)
{
document
.
getElementById
(
'deleteTokenName'
).
textContent
=
tokenName
;
document
.
getElementById
(
'deleteForm'
).
action
=
`/admin/cluster_tokens/
${
tokenId
}
/delete`
;
document
.
getElementById
(
'deleteModal'
).
style
.
display
=
'block'
;
}
<script>
function
deleteToken
(
id
,
name
)
{
if
(
confirm
(
`Are you sure you want to delete the token "
${
name
}
"? This action cannot be undone.`
))
{
// Create a form and submit it
const
form
=
document
.
createElement
(
'form'
);
form
.
method
=
'POST'
;
form
.
action
=
`/admin/cluster_tokens/
${
id
}
/delete`
;
function
closeDeleteModal
()
{
document
.
getElementById
(
'deleteModal'
).
style
.
display
=
'none'
;
}
const
input
=
document
.
createElement
(
'input'
);
input
.
type
=
'hidden'
;
input
.
name
=
'confirm_delete'
;
input
.
value
=
'yes'
;
form
.
appendChild
(
input
);
function
openAddTokenModal
()
{
const
modal
=
document
.
getElementById
(
'addTokenModal'
);
modal
.
style
.
display
=
'block'
;
}
document
.
body
.
appendChild
(
form
);
form
.
submit
();
function
closeAddTokenModal
()
{
const
modal
=
document
.
getElementById
(
'addTokenModal'
);
modal
.
style
.
display
=
'none'
;
}
function
closeTokenModal
()
{
document
.
getElementById
(
'tokenModal'
).
style
.
display
=
'none'
;
window
.
location
.
href
=
'/admin/cluster_tokens'
;
// Redirect to avoid POST resubmission
}
</script>
</div>
function
copyTokenFromModal
()
{
const
tokenText
=
document
.
getElementById
(
'modalTokenText'
);
const
copyBtn
=
document
.
querySelector
(
'#tokenModal .copy-btn'
);
const
originalText
=
copyBtn
.
textContent
;
// Use execCommand for reliable copying
const
textArea
=
document
.
createElement
(
'textarea'
);
textArea
.
value
=
tokenText
.
textContent
.
trim
();
document
.
body
.
appendChild
(
textArea
);
textArea
.
select
();
try
{
document
.
execCommand
(
'copy'
);
copyBtn
.
textContent
=
'Copied!'
;
copyBtn
.
style
.
background
=
'#10b981'
;
setTimeout
(
function
()
{
copyBtn
.
textContent
=
originalText
;
copyBtn
.
style
.
background
=
''
;
},
2000
);
}
catch
(
err
)
{
alert
(
'Failed to copy token. Please select and copy manually.'
);
}
document
.
body
.
removeChild
(
textArea
);
}
// Close modal when clicking outside
window
.
onclick
=
function
(
event
)
{
const
addModal
=
document
.
getElementById
(
'addTokenModal'
);
if
(
addModal
&&
event
.
target
==
addModal
)
{
closeAddTokenModal
();
}
const
deleteModal
=
document
.
getElementById
(
'deleteModal'
);
if
(
deleteModal
&&
event
.
target
==
deleteModal
)
{
closeDeleteModal
();
}
const
tokenModal
=
document
.
getElementById
(
'tokenModal'
);
if
(
tokenModal
&&
event
.
target
==
tokenModal
)
{
closeTokenModal
();
}
}
</script>
{% endblock %}
\ No newline at end of file
vidai/web.py
View file @
a1b6e302
...
...
@@ -436,8 +436,11 @@ def generate_cluster_token():
from
.auth
import
generate_worker_token
token
=
generate_worker_token
(
token_name
)
flash
(
f
'New cluster token "{token_name}" generated: {token[:20]}...'
,
'success'
)
return
redirect
(
url_for
(
'cluster_tokens'
))
# Return with modal data instead of flash
from
.database
import
get_worker_tokens
worker_tokens
=
get_worker_tokens
()
user
=
get_current_user_session
()
return
render_template
(
'admin/cluster_tokens.html'
,
user
=
user
,
worker_tokens
=
worker_tokens
,
generated_token
=
token
,
token_name
=
token_name
,
show_modal
=
True
,
active_page
=
'cluster_tokens'
)
@
app
.
route
(
'/admin/cluster_tokens/<int:token_id>/deactivate'
,
methods
=
[
'POST'
])
@
admin_required
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment