Spaces:
Running
Running
Update Dockerfile
Browse files- Dockerfile +131 -15
Dockerfile
CHANGED
@@ -26,6 +26,119 @@ ENV HOST=0.0.0.0 \
|
|
26 |
SUDO_SECRET="$SUDO_SECRET" \
|
27 |
FLASK_SECRET="$FLASK_SECRET" \
|
28 |
NODE_ENV=production
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
29 |
# ===== Admin Backend =====
|
30 |
COPY <<"EOF" /app/sudo/app.py
|
31 |
from flask import Flask, request, jsonify
|
@@ -36,7 +149,7 @@ import os
|
|
36 |
from datetime import datetime
|
37 |
from functools import wraps
|
38 |
|
39 |
-
app = Flask(__name__)
|
40 |
app.secret_key = os.getenv("FLASK_SECRET")
|
41 |
|
42 |
# MongoDB connection
|
@@ -53,34 +166,37 @@ def sudo_required(f):
|
|
53 |
return f(*args, **kwargs)
|
54 |
return wrapper
|
55 |
|
56 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
57 |
@app.route('/sudo/users', methods=['GET'])
|
58 |
-
@
|
59 |
def list_users():
|
60 |
-
users = list(db.users.find({}, {
|
61 |
-
"_id": 0,
|
62 |
-
"username": 1,
|
63 |
-
"email": 1,
|
64 |
-
"createdAt": 1,
|
65 |
-
"lastLogin": 1
|
66 |
-
}))
|
67 |
return jsonify(users)
|
68 |
|
69 |
@app.route('/sudo/users', methods=['POST'])
|
70 |
-
@
|
71 |
def add_user():
|
72 |
user_data = {
|
73 |
"username": request.json["username"],
|
74 |
"password": generate_password_hash(request.json["password"]),
|
75 |
-
"email": request.json.get("email", ""),
|
76 |
-
"createdAt": datetime.utcnow(),
|
77 |
"role": "user"
|
78 |
}
|
79 |
db.users.insert_one(user_data)
|
80 |
-
return jsonify({"status": "User added"})
|
81 |
|
82 |
@app.route('/sudo/users/<username>', methods=['DELETE'])
|
83 |
-
@
|
84 |
def delete_user(username):
|
85 |
result = db.users.delete_one({"username": username})
|
86 |
if result.deleted_count == 0:
|
|
|
26 |
SUDO_SECRET="$SUDO_SECRET" \
|
27 |
FLASK_SECRET="$FLASK_SECRET" \
|
28 |
NODE_ENV=production
|
29 |
+
#==========================
|
30 |
+
# ===== HTML Admin Panel =====
|
31 |
+
COPY <<"EOF" /app/sudo/templates/index.html
|
32 |
+
<!DOCTYPE html>
|
33 |
+
<html>
|
34 |
+
<head>
|
35 |
+
<title>LibreChat Admin</title>
|
36 |
+
<style>
|
37 |
+
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; }
|
38 |
+
.container { max-width: 1000px; margin: 0 auto; }
|
39 |
+
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
|
40 |
+
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }
|
41 |
+
button { padding: 6px 12px; cursor: pointer; }
|
42 |
+
.login-form { max-width: 400px; margin: 50px auto; }
|
43 |
+
</style>
|
44 |
+
</head>
|
45 |
+
<body>
|
46 |
+
<div id="login" class="login-form" style="display: block;">
|
47 |
+
<h2>Admin Login</h2>
|
48 |
+
<input type="password" id="password" placeholder="Admin Password">
|
49 |
+
<button onclick="login()">Login</button>
|
50 |
+
</div>
|
51 |
+
|
52 |
+
<div id="admin-panel" class="container" style="display: none;">
|
53 |
+
<h1>User Management</h1>
|
54 |
+
<div>
|
55 |
+
<input type="text" id="new-username" placeholder="Username">
|
56 |
+
<input type="password" id="new-password" placeholder="Password">
|
57 |
+
<button onclick="addUser()">Add User</button>
|
58 |
+
</div>
|
59 |
+
<table id="users-table">
|
60 |
+
<thead>
|
61 |
+
<tr>
|
62 |
+
<th>Username</th>
|
63 |
+
<th>Actions</th>
|
64 |
+
</tr>
|
65 |
+
</thead>
|
66 |
+
<tbody></tbody>
|
67 |
+
</table>
|
68 |
+
</div>
|
69 |
+
|
70 |
+
<script>
|
71 |
+
let authToken = '';
|
72 |
+
|
73 |
+
async function login() {
|
74 |
+
const password = document.getElementById('password').value;
|
75 |
+
const response = await fetch('/sudo/login', {
|
76 |
+
method: 'POST',
|
77 |
+
headers: { 'Content-Type': 'application/json' },
|
78 |
+
body: JSON.stringify({ password })
|
79 |
+
});
|
80 |
+
|
81 |
+
if (response.ok) {
|
82 |
+
const data = await response.json();
|
83 |
+
authToken = data.token;
|
84 |
+
document.getElementById('login').style.display = 'none';
|
85 |
+
document.getElementById('admin-panel').style.display = 'block';
|
86 |
+
loadUsers();
|
87 |
+
} else {
|
88 |
+
alert('Login failed!');
|
89 |
+
}
|
90 |
+
}
|
91 |
+
|
92 |
+
async function loadUsers() {
|
93 |
+
const response = await fetch('/sudo/users', {
|
94 |
+
headers: { 'X-Auth-Token': authToken }
|
95 |
+
});
|
96 |
+
const users = await response.json();
|
97 |
+
|
98 |
+
const tbody = document.querySelector('#users-table tbody');
|
99 |
+
tbody.innerHTML = users.map(user => `
|
100 |
+
<tr>
|
101 |
+
<td>${user.username}</td>
|
102 |
+
<td>
|
103 |
+
<button onclick="deleteUser('${user.username}')">Delete</button>
|
104 |
+
</td>
|
105 |
+
</tr>
|
106 |
+
`).join('');
|
107 |
+
}
|
108 |
+
|
109 |
+
async function addUser() {
|
110 |
+
const username = document.getElementById('new-username').value;
|
111 |
+
const password = document.getElementById('new-password').value;
|
112 |
+
|
113 |
+
const response = await fetch('/sudo/users', {
|
114 |
+
method: 'POST',
|
115 |
+
headers: {
|
116 |
+
'Content-Type': 'application/json',
|
117 |
+
'X-Auth-Token': authToken
|
118 |
+
},
|
119 |
+
body: JSON.stringify({ username, password })
|
120 |
+
});
|
121 |
+
|
122 |
+
if (response.ok) {
|
123 |
+
loadUsers();
|
124 |
+
document.getElementById('new-username').value = '';
|
125 |
+
document.getElementById('new-password').value = '';
|
126 |
+
}
|
127 |
+
}
|
128 |
+
|
129 |
+
async function deleteUser(username) {
|
130 |
+
if (confirm(`Delete ${username}?`)) {
|
131 |
+
await fetch(`/sudo/users/${username}`, {
|
132 |
+
method: 'DELETE',
|
133 |
+
headers: { 'X-Auth-Token': authToken }
|
134 |
+
});
|
135 |
+
loadUsers();
|
136 |
+
}
|
137 |
+
}
|
138 |
+
</script>
|
139 |
+
</body>
|
140 |
+
</html>
|
141 |
+
EOF
|
142 |
# ===== Admin Backend =====
|
143 |
COPY <<"EOF" /app/sudo/app.py
|
144 |
from flask import Flask, request, jsonify
|
|
|
149 |
from datetime import datetime
|
150 |
from functools import wraps
|
151 |
|
152 |
+
app = Flask(__name__, template_folder='/app/sudo/templates')
|
153 |
app.secret_key = os.getenv("FLASK_SECRET")
|
154 |
|
155 |
# MongoDB connection
|
|
|
166 |
return f(*args, **kwargs)
|
167 |
return wrapper
|
168 |
|
169 |
+
|
170 |
+
# Routes
|
171 |
+
@app.route('/sudo')
|
172 |
+
def admin_panel():
|
173 |
+
return render_template('index.html')
|
174 |
+
|
175 |
+
@app.route('/sudo/login', methods=['POST'])
|
176 |
+
def login():
|
177 |
+
if not hmac.compare_digest(request.json.get('password') or '', ADMIN_SECRET):
|
178 |
+
return jsonify({"error": "Invalid credentials"}), 401
|
179 |
+
return jsonify({"token": ADMIN_SECRET})
|
180 |
+
|
181 |
@app.route('/sudo/users', methods=['GET'])
|
182 |
+
@require_auth
|
183 |
def list_users():
|
184 |
+
users = list(db.users.find({}, {"_id": 0, "username": 1}))
|
|
|
|
|
|
|
|
|
|
|
|
|
185 |
return jsonify(users)
|
186 |
|
187 |
@app.route('/sudo/users', methods=['POST'])
|
188 |
+
@require_auth
|
189 |
def add_user():
|
190 |
user_data = {
|
191 |
"username": request.json["username"],
|
192 |
"password": generate_password_hash(request.json["password"]),
|
|
|
|
|
193 |
"role": "user"
|
194 |
}
|
195 |
db.users.insert_one(user_data)
|
196 |
+
return jsonify({"status": "User added"})
|
197 |
|
198 |
@app.route('/sudo/users/<username>', methods=['DELETE'])
|
199 |
+
@require_auth
|
200 |
def delete_user(username):
|
201 |
result = db.users.delete_one({"username": username})
|
202 |
if result.deleted_count == 0:
|