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
b2af681f
Commit
b2af681f
authored
Oct 06, 2025
by
Stefy Lanza (nextime / spora )
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update cluster tokens to be managed like API tokens with names, last_used, and delete functionality
parent
5593a42e
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
97 additions
and
25 deletions
+97
-25
cluster_tokens.html
templates/admin/cluster_tokens.html
+39
-5
auth.py
vidai/auth.py
+2
-2
database.py
vidai/database.py
+31
-7
web.py
vidai/web.py
+25
-11
No files found.
templates/admin/cluster_tokens.html
View file @
b2af681f
...
@@ -45,27 +45,40 @@
...
@@ -45,27 +45,40 @@
{% endif %}
{% endif %}
{% endwith %}
{% endwith %}
<form
method=
"post"
action=
"/admin/cluster_tokens/generate"
style=
"margin-bottom: 2rem;"
>
<div
class=
"admin-card"
style=
"margin-bottom: 2rem;"
>
<button
type=
"submit"
class=
"btn"
><i
class=
"fas fa-plus"
></i>
Generate New Token
</button>
<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>
</form>
</div>
<table
class=
"table"
>
<table
class=
"table"
>
<thead>
<thead>
<tr>
<tr>
<th>
ID
</th>
<th>
Name
</th>
<th>
Token
</th>
<th>
Token
</th>
<th>
Status
</th>
<th>
Status
</th>
<th>
Created
</th>
<th>
Created
</th>
<th>
Last Used
</th>
<th>
Actions
</th>
<th>
Actions
</th>
</tr>
</tr>
</thead>
</thead>
<tbody>
<tbody>
{% for token in worker_tokens %}
{% for token in worker_tokens %}
<tr>
<tr>
<td>
{{ token.get('
id
') }}
</td>
<td>
{{ token.get('
name
') }}
</td>
<td><span
class=
"token-preview"
>
{{ token.get('token')[:20] }}...
</span></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><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('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
class=
"actions-cell"
>
{% if token.get('active') %}
{% if token.get('active') %}
<form
method=
"post"
action=
"/admin/cluster_tokens/{{ token.get('id') }}/deactivate"
style=
"display: inline;"
>
<form
method=
"post"
action=
"/admin/cluster_tokens/{{ token.get('id') }}/deactivate"
style=
"display: inline;"
>
...
@@ -76,6 +89,7 @@
...
@@ -76,6 +89,7 @@
<button
type=
"submit"
class=
"btn btn-success"
title=
"Activate"
style=
"padding: 0.25rem 0.5rem; font-size: 0.75rem;"
>
Activate
</button>
<button
type=
"submit"
class=
"btn btn-success"
title=
"Activate"
style=
"padding: 0.25rem 0.5rem; font-size: 0.75rem;"
>
Activate
</button>
</form>
</form>
{% endif %}
{% 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>
</td>
</td>
</tr>
</tr>
{% endfor %}
{% endfor %}
...
@@ -86,5 +100,25 @@
...
@@ -86,5 +100,25 @@
<p
style=
"text-align: center; color: #64748b; margin-top: 2rem;"
>
No cluster tokens found. Generate your first token above.
</p>
<p
style=
"text-align: center; color: #64748b; margin-top: 2rem;"
>
No cluster tokens found. Generate your first token above.
</p>
{% endif %}
{% endif %}
</div>
</div>
<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`
;
const
input
=
document
.
createElement
(
'input'
);
input
.
type
=
'hidden'
;
input
.
name
=
'confirm_delete'
;
input
.
value
=
'yes'
;
form
.
appendChild
(
input
);
document
.
body
.
appendChild
(
form
);
form
.
submit
();
}
}
</script>
</div>
</div>
{% endblock %}
{% endblock %}
\ No newline at end of file
vidai/auth.py
View file @
b2af681f
...
@@ -173,10 +173,10 @@ def generate_api_token(user_id: int) -> str:
...
@@ -173,10 +173,10 @@ def generate_api_token(user_id: int) -> str:
return
create_api_token
(
user_id
)
return
create_api_token
(
user_id
)
def
generate_worker_token
()
->
str
:
def
generate_worker_token
(
name
:
str
)
->
str
:
"""Generate worker authentication token (admin only)."""
"""Generate worker authentication token (admin only)."""
from
.database
import
create_worker_token
from
.database
import
create_worker_token
return
create_worker_token
()
return
create_worker_token
(
name
)
def
generate_user_api_token
(
user_id
:
int
)
->
str
:
def
generate_user_api_token
(
user_id
:
int
)
->
str
:
...
...
vidai/database.py
View file @
b2af681f
...
@@ -330,20 +330,33 @@ def init_db(conn) -> None:
...
@@ -330,20 +330,33 @@ def init_db(conn) -> None:
cursor
.
execute
(
'''
cursor
.
execute
(
'''
CREATE TABLE IF NOT EXISTS worker_tokens (
CREATE TABLE IF NOT EXISTS worker_tokens (
id INT AUTO_INCREMENT PRIMARY KEY,
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
token VARCHAR(255) UNIQUE NOT NULL,
token VARCHAR(255) UNIQUE NOT NULL,
active BOOLEAN DEFAULT 1,
active BOOLEAN DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_used TIMESTAMP NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
'''
)
'''
)
else
:
else
:
cursor
.
execute
(
'''
cursor
.
execute
(
'''
CREATE TABLE IF NOT EXISTS worker_tokens (
CREATE TABLE IF NOT EXISTS worker_tokens (
id INTEGER PRIMARY KEY,
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
token TEXT UNIQUE NOT NULL,
token TEXT UNIQUE NOT NULL,
active BOOLEAN DEFAULT 1,
active BOOLEAN DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_used TIMESTAMP
)
)
'''
)
'''
)
# Add missing columns if they don't exist
try
:
cursor
.
execute
(
'ALTER TABLE worker_tokens ADD COLUMN name TEXT'
)
except
sqlite3
.
OperationalError
:
pass
try
:
cursor
.
execute
(
'ALTER TABLE worker_tokens ADD COLUMN last_used TIMESTAMP'
)
except
sqlite3
.
OperationalError
:
pass
# Sessions table for persistent sessions
# Sessions table for persistent sessions
if
config
[
'type'
]
==
'mysql'
:
if
config
[
'type'
]
==
'mysql'
:
...
@@ -1120,7 +1133,7 @@ def delete_user(user_id: int) -> bool:
...
@@ -1120,7 +1133,7 @@ def delete_user(user_id: int) -> bool:
return
success
return
success
def
create_worker_token
()
->
str
:
def
create_worker_token
(
name
:
str
)
->
str
:
"""Create a worker authentication token."""
"""Create a worker authentication token."""
import
secrets
import
secrets
token
=
secrets
.
token_hex
(
32
)
token
=
secrets
.
token_hex
(
32
)
...
@@ -1129,9 +1142,9 @@ def create_worker_token() -> str:
...
@@ -1129,9 +1142,9 @@ def create_worker_token() -> str:
cursor
=
conn
.
cursor
()
cursor
=
conn
.
cursor
()
cursor
.
execute
(
'''
cursor
.
execute
(
'''
INSERT INTO worker_tokens (token, created_at, active)
INSERT INTO worker_tokens (
name,
token, created_at, active)
VALUES (?, datetime('now'), 1)
VALUES (?,
?,
datetime('now'), 1)
'''
,
(
token
,
))
'''
,
(
name
,
token
))
conn
.
commit
()
conn
.
commit
()
conn
.
close
()
conn
.
close
()
...
@@ -1142,7 +1155,7 @@ def get_worker_tokens() -> List[Dict[str, Any]]:
...
@@ -1142,7 +1155,7 @@ def get_worker_tokens() -> List[Dict[str, Any]]:
"""Get all worker tokens."""
"""Get all worker tokens."""
conn
=
get_db_connection
()
conn
=
get_db_connection
()
cursor
=
conn
.
cursor
()
cursor
=
conn
.
cursor
()
cursor
.
execute
(
'SELECT id,
token, active, created_at
FROM worker_tokens ORDER BY created_at DESC'
)
cursor
.
execute
(
'SELECT id,
name, token, active, created_at, last_used
FROM worker_tokens ORDER BY created_at DESC'
)
rows
=
cursor
.
fetchall
()
rows
=
cursor
.
fetchall
()
conn
.
close
()
conn
.
close
()
return
[
dict
(
row
)
for
row
in
rows
]
return
[
dict
(
row
)
for
row
in
rows
]
...
@@ -1170,6 +1183,17 @@ def activate_worker_token(token_id: int) -> bool:
...
@@ -1170,6 +1183,17 @@ def activate_worker_token(token_id: int) -> bool:
return
success
return
success
def
delete_worker_token
(
token_id
:
int
)
->
bool
:
"""Delete a worker token."""
conn
=
get_db_connection
()
cursor
=
conn
.
cursor
()
cursor
.
execute
(
'DELETE FROM worker_tokens WHERE id = ?'
,
(
token_id
,))
conn
.
commit
()
success
=
cursor
.
rowcount
>
0
conn
.
close
()
return
success
def
create_user_api_token
(
user_id
:
int
,
name
:
str
)
->
str
:
def
create_user_api_token
(
user_id
:
int
,
name
:
str
)
->
str
:
"""Create a user API token for programmatic access."""
"""Create a user API token for programmatic access."""
import
jwt
import
jwt
...
...
vidai/web.py
View file @
b2af681f
...
@@ -409,15 +409,6 @@ def update_settings():
...
@@ -409,15 +409,6 @@ def update_settings():
flash
(
'Settings updated successfully!'
,
'success'
)
flash
(
'Settings updated successfully!'
,
'success'
)
return
redirect
(
url_for
(
'settings'
))
return
redirect
(
url_for
(
'settings'
))
@
app
.
route
(
'/generate_worker_token'
)
@
admin_required
def
generate_worker_token
():
"""Generate a new worker authentication token (admin only)."""
from
.auth
import
generate_worker_token
token
=
generate_worker_token
()
flash
(
f
'New worker token generated: {token[:20]}...'
,
'success'
)
return
redirect
(
url_for
(
'cluster_tokens'
))
@
app
.
route
(
'/admin/cluster_tokens'
)
@
app
.
route
(
'/admin/cluster_tokens'
)
@
admin_required
@
admin_required
def
cluster_tokens
():
def
cluster_tokens
():
...
@@ -431,9 +422,21 @@ def cluster_tokens():
...
@@ -431,9 +422,21 @@ def cluster_tokens():
@
admin_required
@
admin_required
def
generate_cluster_token
():
def
generate_cluster_token
():
"""Generate a new cluster token."""
"""Generate a new cluster token."""
token_name
=
request
.
form
.
get
(
'token_name'
,
''
)
.
strip
()
if
not
token_name
:
flash
(
'Token name is required'
,
'error'
)
return
redirect
(
url_for
(
'cluster_tokens'
))
# Check if name already exists
from
.database
import
get_worker_tokens
existing
=
get_worker_tokens
()
if
any
(
t
.
get
(
'name'
)
==
token_name
for
t
in
existing
):
flash
(
'A token with this name already exists. Please choose a different name.'
,
'error'
)
return
redirect
(
url_for
(
'cluster_tokens'
))
from
.auth
import
generate_worker_token
from
.auth
import
generate_worker_token
token
=
generate_worker_token
()
token
=
generate_worker_token
(
token_name
)
flash
(
f
'New cluster token generated: {token[:20]}...'
,
'success'
)
flash
(
f
'New cluster token
"{token_name}"
generated: {token[:20]}...'
,
'success'
)
return
redirect
(
url_for
(
'cluster_tokens'
))
return
redirect
(
url_for
(
'cluster_tokens'
))
@
app
.
route
(
'/admin/cluster_tokens/<int:token_id>/deactivate'
,
methods
=
[
'POST'
])
@
app
.
route
(
'/admin/cluster_tokens/<int:token_id>/deactivate'
,
methods
=
[
'POST'
])
...
@@ -458,6 +461,17 @@ def activate_cluster_token(token_id):
...
@@ -458,6 +461,17 @@ def activate_cluster_token(token_id):
flash
(
'Failed to activate token.'
,
'error'
)
flash
(
'Failed to activate token.'
,
'error'
)
return
redirect
(
url_for
(
'cluster_tokens'
))
return
redirect
(
url_for
(
'cluster_tokens'
))
@
app
.
route
(
'/admin/cluster_tokens/<int:token_id>/delete'
,
methods
=
[
'POST'
])
@
admin_required
def
delete_cluster_token
(
token_id
):
"""Delete a cluster token."""
from
.database
import
delete_worker_token
if
delete_worker_token
(
token_id
):
flash
(
'Token deleted successfully!'
,
'success'
)
else
:
flash
(
'Failed to delete token.'
,
'error'
)
return
redirect
(
url_for
(
'cluster_tokens'
))
@
app
.
route
(
'/api_tokens'
)
@
app
.
route
(
'/api_tokens'
)
@
login_required
@
login_required
def
api_tokens
():
def
api_tokens
():
...
...
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