Spaces:
Sleeping
Sleeping
Commit
Β·
fb7f347
1
Parent(s):
c7f27a3
feat: Add secret view
Browse files- app/api/autoreg.py +4 -1
- app/static/index.html +97 -4
- app/websocket.py +4 -1
app/api/autoreg.py
CHANGED
|
@@ -38,7 +38,10 @@ async def _broadcast_accounts_update(ws):
|
|
| 38 |
"tokenData": {
|
| 39 |
"accountName": t.account_name,
|
| 40 |
"email": t.email,
|
| 41 |
-
"expiresAt": t.expires_at.isoformat() if t.expires_at else None
|
|
|
|
|
|
|
|
|
|
| 42 |
},
|
| 43 |
"usage": {
|
| 44 |
"currentUsage": -1,
|
|
|
|
| 38 |
"tokenData": {
|
| 39 |
"accountName": t.account_name,
|
| 40 |
"email": t.email,
|
| 41 |
+
"expiresAt": t.expires_at.isoformat() if t.expires_at else None,
|
| 42 |
+
"refreshToken": t.raw_data.get('refreshToken') if t.raw_data else None,
|
| 43 |
+
"clientId": t.raw_data.get('_clientId') if t.raw_data else None,
|
| 44 |
+
"clientSecret": t.raw_data.get('_clientSecret') if t.raw_data else None
|
| 45 |
},
|
| 46 |
"usage": {
|
| 47 |
"currentUsage": -1,
|
app/static/index.html
CHANGED
|
@@ -6671,8 +6671,40 @@
|
|
| 6671 |
<textarea class="modal-textarea" id="ssoTokenInput" placeholder="Paste cookie..."></textarea>
|
| 6672 |
<button class="btn btn-primary" style="width:100%" onclick="importSsoToken()">Import</button>
|
| 6673 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6674 |
</div>
|
| 6675 |
</div>
|
|
|
|
|
|
|
|
|
|
| 6676 |
|
| 6677 |
|
| 6678 |
<div class="dialog-overlay" id="dialogOverlay" onclick="if(event.target === this) closeDialog()">
|
|
@@ -6952,6 +6984,8 @@ let pendingAction = null;
|
|
| 6952 |
|
| 6953 |
|
| 6954 |
const STATE = {"filter":"all","sort":"email","compact":false,"settingsOpen":false,"searchQuery":"","scrollPosition":0,"activeTab":"accounts","disableToasts":false};
|
|
|
|
|
|
|
| 6955 |
|
| 6956 |
function getState() { return STATE; }
|
| 6957 |
|
|
@@ -7453,6 +7487,59 @@ let pendingAction = null;
|
|
| 7453 |
closeSsoModal();
|
| 7454 |
}
|
| 7455 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7456 |
|
| 7457 |
// === Console Drawer ===
|
| 7458 |
|
|
@@ -8053,6 +8140,10 @@ let pendingAction = null;
|
|
| 8053 |
case 'accountsLoaded':
|
| 8054 |
// Standalone WebSocket sends accountsLoaded with accounts array
|
| 8055 |
if (msg.accounts && Array.isArray(msg.accounts)) {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8056 |
const list = document.getElementById('accountList');
|
| 8057 |
if (list) {
|
| 8058 |
if (msg.accounts.length === 0) {
|
|
@@ -8064,11 +8155,12 @@ let pendingAction = null;
|
|
| 8064 |
const statusClass = acc.isExpired ? 'expired' : (acc.needsRefresh ? 'warning' : 'valid');
|
| 8065 |
const name = acc.tokenData?.accountName || acc.filename || 'Unknown';
|
| 8066 |
const email = acc.tokenData?.email || '';
|
|
|
|
| 8067 |
const usage = acc.usage || {};
|
| 8068 |
const usageText = usage.currentUsage >= 0 ? usage.currentUsage + '/' + (usage.usageLimit || 500) : 'β';
|
| 8069 |
const usagePercent = usage.percentageUsed || 0;
|
| 8070 |
|
| 8071 |
-
return '<div class="account ' + isActive + ' ' + statusClass + '" data-filename="' + acc.filename + '" onclick="switchAccount(\'' +
|
| 8072 |
'<div class="account-info">' +
|
| 8073 |
'<div class="account-name">' + name + '</div>' +
|
| 8074 |
'<div class="account-email">' + email + '</div>' +
|
|
@@ -8078,8 +8170,9 @@ let pendingAction = null;
|
|
| 8078 |
'<div class="usage-text">' + usageText + '</div>' +
|
| 8079 |
'</div>' +
|
| 8080 |
'<div class="account-actions">' +
|
| 8081 |
-
'<button class="btn btn-sm" onclick="event.stopPropagation(); refreshToken(\'' +
|
| 8082 |
-
'<button class="btn btn-sm
|
|
|
|
| 8083 |
'</div>' +
|
| 8084 |
'</div>';
|
| 8085 |
}).join('');
|
|
@@ -9426,4 +9519,4 @@ let pendingAction = null;
|
|
| 9426 |
window.updateScheduledRegState = updateScheduledRegState;
|
| 9427 |
</script>
|
| 9428 |
</body>
|
| 9429 |
-
</html>
|
|
|
|
| 6671 |
<textarea class="modal-textarea" id="ssoTokenInput" placeholder="Paste cookie..."></textarea>
|
| 6672 |
<button class="btn btn-primary" style="width:100%" onclick="importSsoToken()">Import</button>
|
| 6673 |
</div>
|
| 6674 |
+
|
| 6675 |
+
<div class="modal-overlay" id="accountSecretsModal" onclick="if(event.target === this) closeAccountSecrets()">
|
| 6676 |
+
<div class="modal">
|
| 6677 |
+
<div class="modal-header">
|
| 6678 |
+
<span class="modal-title">Account Secrets</span>
|
| 6679 |
+
<button class="modal-close" onclick="closeAccountSecrets()">ΠΠ</button>
|
| 6680 |
+
</div>
|
| 6681 |
+
<div class="modal-body">
|
| 6682 |
+
<div class="form-group">
|
| 6683 |
+
<label class="form-label">Email</label>
|
| 6684 |
+
<textarea class="modal-textarea" id="accountSecretEmail" readonly></textarea>
|
| 6685 |
+
<button class="btn btn-secondary" style="width:100%" onclick="copySecretField('accountSecretEmail')">Copy</button>
|
| 6686 |
+
</div>
|
| 6687 |
+
<div class="form-group">
|
| 6688 |
+
<label class="form-label">RefreshToken</label>
|
| 6689 |
+
<textarea class="modal-textarea" id="accountSecretRefreshToken" readonly></textarea>
|
| 6690 |
+
<button class="btn btn-secondary" style="width:100%" onclick="copySecretField('accountSecretRefreshToken')">Copy</button>
|
| 6691 |
+
</div>
|
| 6692 |
+
<div class="form-group">
|
| 6693 |
+
<label class="form-label">Client ID</label>
|
| 6694 |
+
<textarea class="modal-textarea" id="accountSecretClientId" readonly></textarea>
|
| 6695 |
+
<button class="btn btn-secondary" style="width:100%" onclick="copySecretField('accountSecretClientId')">Copy</button>
|
| 6696 |
+
</div>
|
| 6697 |
+
<div class="form-group">
|
| 6698 |
+
<label class="form-label">Client Secret</label>
|
| 6699 |
+
<textarea class="modal-textarea" id="accountSecretClientSecret" readonly></textarea>
|
| 6700 |
+
<button class="btn btn-secondary" style="width:100%" onclick="copySecretField('accountSecretClientSecret')">Copy</button>
|
| 6701 |
+
</div>
|
| 6702 |
+
</div>
|
| 6703 |
</div>
|
| 6704 |
</div>
|
| 6705 |
+
|
| 6706 |
+
</div>
|
| 6707 |
+
</div>
|
| 6708 |
|
| 6709 |
|
| 6710 |
<div class="dialog-overlay" id="dialogOverlay" onclick="if(event.target === this) closeDialog()">
|
|
|
|
| 6984 |
|
| 6985 |
|
| 6986 |
const STATE = {"filter":"all","sort":"email","compact":false,"settingsOpen":false,"searchQuery":"","scrollPosition":0,"activeTab":"accounts","disableToasts":false};
|
| 6987 |
+
|
| 6988 |
+
let ACCOUNT_INDEX = {};
|
| 6989 |
|
| 6990 |
function getState() { return STATE; }
|
| 6991 |
|
|
|
|
| 7487 |
closeSsoModal();
|
| 7488 |
}
|
| 7489 |
}
|
| 7490 |
+
// === Account Secrets Modal ===
|
| 7491 |
+
|
| 7492 |
+
function openAccountSecrets(filename) {
|
| 7493 |
+
const modal = document.getElementById('accountSecretsModal');
|
| 7494 |
+
const acc = ACCOUNT_INDEX[filename];
|
| 7495 |
+
if (!modal || !acc) return;
|
| 7496 |
+
|
| 7497 |
+
const tokenData = acc.tokenData || {};
|
| 7498 |
+
const email = tokenData.email || '';
|
| 7499 |
+
const refreshToken = tokenData.refreshToken || '';
|
| 7500 |
+
const clientId = tokenData.clientId || '';
|
| 7501 |
+
const clientSecret = tokenData.clientSecret || '';
|
| 7502 |
+
|
| 7503 |
+
const emailEl = document.getElementById('accountSecretEmail');
|
| 7504 |
+
const refreshEl = document.getElementById('accountSecretRefreshToken');
|
| 7505 |
+
const clientIdEl = document.getElementById('accountSecretClientId');
|
| 7506 |
+
const clientSecretEl = document.getElementById('accountSecretClientSecret');
|
| 7507 |
+
|
| 7508 |
+
if (emailEl) emailEl.value = email;
|
| 7509 |
+
if (refreshEl) refreshEl.value = refreshToken;
|
| 7510 |
+
if (clientIdEl) clientIdEl.value = clientId;
|
| 7511 |
+
if (clientSecretEl) clientSecretEl.value = clientSecret;
|
| 7512 |
+
|
| 7513 |
+
modal.classList.add('visible');
|
| 7514 |
+
}
|
| 7515 |
+
|
| 7516 |
+
function closeAccountSecrets() {
|
| 7517 |
+
const modal = document.getElementById('accountSecretsModal');
|
| 7518 |
+
modal?.classList.remove('visible');
|
| 7519 |
+
}
|
| 7520 |
+
|
| 7521 |
+
function copySecretField(id) {
|
| 7522 |
+
const el = document.getElementById(id);
|
| 7523 |
+
const value = el?.value || '';
|
| 7524 |
+
if (!value) return;
|
| 7525 |
+
if (navigator.clipboard?.writeText) {
|
| 7526 |
+
navigator.clipboard.writeText(value).then(() => {
|
| 7527 |
+
showToast('Copied', 'success');
|
| 7528 |
+
}).catch(() => {
|
| 7529 |
+
showToast('Copy failed', 'error');
|
| 7530 |
+
});
|
| 7531 |
+
} else {
|
| 7532 |
+
try {
|
| 7533 |
+
el.focus();
|
| 7534 |
+
el.select();
|
| 7535 |
+
document.execCommand('copy');
|
| 7536 |
+
showToast('Copied', 'success');
|
| 7537 |
+
} catch {
|
| 7538 |
+
showToast('Copy failed', 'error');
|
| 7539 |
+
}
|
| 7540 |
+
}
|
| 7541 |
+
}
|
| 7542 |
+
|
| 7543 |
|
| 7544 |
// === Console Drawer ===
|
| 7545 |
|
|
|
|
| 8140 |
case 'accountsLoaded':
|
| 8141 |
// Standalone WebSocket sends accountsLoaded with accounts array
|
| 8142 |
if (msg.accounts && Array.isArray(msg.accounts)) {
|
| 8143 |
+
ACCOUNT_INDEX = {};
|
| 8144 |
+
msg.accounts.forEach(function(acc) {
|
| 8145 |
+
ACCOUNT_INDEX[acc.filename] = acc;
|
| 8146 |
+
});
|
| 8147 |
const list = document.getElementById('accountList');
|
| 8148 |
if (list) {
|
| 8149 |
if (msg.accounts.length === 0) {
|
|
|
|
| 8155 |
const statusClass = acc.isExpired ? 'expired' : (acc.needsRefresh ? 'warning' : 'valid');
|
| 8156 |
const name = acc.tokenData?.accountName || acc.filename || 'Unknown';
|
| 8157 |
const email = acc.tokenData?.email || '';
|
| 8158 |
+
const safeFilename = (acc.filename || '').replace(/'/g, "\\'");
|
| 8159 |
const usage = acc.usage || {};
|
| 8160 |
const usageText = usage.currentUsage >= 0 ? usage.currentUsage + '/' + (usage.usageLimit || 500) : 'β';
|
| 8161 |
const usagePercent = usage.percentageUsed || 0;
|
| 8162 |
|
| 8163 |
+
return '<div class="account ' + isActive + ' ' + statusClass + '" data-filename="' + acc.filename + '" onclick="switchAccount(\'' + safeFilename + '\')">' +
|
| 8164 |
'<div class="account-info">' +
|
| 8165 |
'<div class="account-name">' + name + '</div>' +
|
| 8166 |
'<div class="account-email">' + email + '</div>' +
|
|
|
|
| 8170 |
'<div class="usage-text">' + usageText + '</div>' +
|
| 8171 |
'</div>' +
|
| 8172 |
'<div class="account-actions">' +
|
| 8173 |
+
'<button class="btn btn-sm" onclick="event.stopPropagation(); refreshToken(\'' + safeFilename + '\')">π</button>' +
|
| 8174 |
+
'<button class="btn btn-sm" onclick="event.stopPropagation(); openAccountSecrets(\'' + safeFilename + '\')">π</button>' +
|
| 8175 |
+
'<button class="btn btn-sm btn-danger" onclick="event.stopPropagation(); confirmDelete(\'' + safeFilename + '\')">ποΈ</button>' +
|
| 8176 |
'</div>' +
|
| 8177 |
'</div>';
|
| 8178 |
}).join('');
|
|
|
|
| 9519 |
window.updateScheduledRegState = updateScheduledRegState;
|
| 9520 |
</script>
|
| 9521 |
</body>
|
| 9522 |
+
</html>
|
app/websocket.py
CHANGED
|
@@ -170,7 +170,10 @@ async def handle_command(command: str, data: Dict[str, Any], websocket: WebSocke
|
|
| 170 |
"tokenData": {
|
| 171 |
"accountName": token.account_name,
|
| 172 |
"email": token.email,
|
| 173 |
-
"expiresAt": token.expires_at.isoformat() if token.expires_at else None
|
|
|
|
|
|
|
|
|
|
| 174 |
},
|
| 175 |
"usage": {
|
| 176 |
"currentUsage": -1,
|
|
|
|
| 170 |
"tokenData": {
|
| 171 |
"accountName": token.account_name,
|
| 172 |
"email": token.email,
|
| 173 |
+
"expiresAt": token.expires_at.isoformat() if token.expires_at else None,
|
| 174 |
+
"refreshToken": token.raw_data.get('refreshToken') if token.raw_data else None,
|
| 175 |
+
"clientId": token.raw_data.get('_clientId') if token.raw_data else None,
|
| 176 |
+
"clientSecret": token.raw_data.get('_clientSecret') if token.raw_data else None
|
| 177 |
},
|
| 178 |
"usage": {
|
| 179 |
"currentUsage": -1,
|