Add files via upload

adding dashboard copy link
adding copy get route for dashboard
This commit is contained in:
Alpha Nerd 2025-09-04 10:39:10 +02:00 committed by GitHub
parent 190fa874c7
commit 2f09dbe22c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 82 additions and 21 deletions

View file

@ -679,20 +679,38 @@ async def copy_proxy(request: Request):
# 3. Iterate over all endpoints to copy the model on each endpoint
status_list = []
for endpoint in config.endpoints:
client = ollama.AsyncClient(host=endpoint)
# 4. Proxy a simple copy request
copy = await client.copy(source=src, destination=dst)
status_list.append(copy.status)
if "/v1" not in endpoint:
client = ollama.AsyncClient(host=endpoint)
# 4. Proxy a simple copy request
copy = await client.copy(source=src, destination=dst)
status_list.append(copy.status)
# 4. Return with 200 OK if all went well, 404 if a single endpoint failed
if 404 in status_list:
return Response(
status_code=404
)
else:
return Response(
status_code=200
)
return Response(status_code=404 if 404 in status_list else 200)
@app.get("/api/copy")
async def copy_proxy_from_dashboard(source: str, destination: str):
"""
Proxy a model copy request to each Ollama endpoint and reply with a status code.
Accepts `source` and `destination` exclusively as querystring parameters.
"""
# 1. Validate that both values are nonempty strings (FastAPI already guarantees presence)
if not source:
raise HTTPException(status_code=400, detail="Missing required query parameter 'source'")
if not destination:
raise HTTPException(status_code=400, detail="Missing required query parameter 'destination'")
# 2. Iterate over all endpoints to copy the model on each endpoint
status_list = []
for endpoint in config.endpoints:
if "/v1" not in endpoint:
client = ollama.AsyncClient(host=endpoint)
# 3. Proxy a simple copy request
copy = await client.copy(source=source, destination=destination)
status_list.append(copy.status)
# 4. Return with 200 OK if all went well, 404 if any endpoint failed
return Response(status_code=404 if 404 in status_list else 200)
# -------------------------------------------------------------
# 13. API route Delete
@ -720,10 +738,11 @@ async def delete_proxy(request: Request):
# 2. Iterate over all endpoints to delete the model on each endpoint
status_list = []
for endpoint in config.endpoints:
client = ollama.AsyncClient(host=endpoint)
# 3. Proxy a simple copy request
copy = await client.delete(model=model)
status_list.append(copy.status)
if "/v1" not in endpoint:
client = ollama.AsyncClient(host=endpoint)
# 3. Proxy a simple copy request
copy = await client.delete(model=model)
status_list.append(copy.status)
# 4. Retrun 200 0K, if a single enpoint fails, respond with 404
if 404 in status_list:
@ -1005,7 +1024,6 @@ async def openai_chat_completions_proxy(request: Request):
params = {
"messages": messages,
"model": model,
"seed": seed,
"stop": stop,
"stream": stream,
}
@ -1024,6 +1042,8 @@ async def openai_chat_completions_proxy(request: Request):
params["temperature"] = temperature
if top_p is not None:
params["top_p"] = top_p
if seed is not None:
params["seed"] = seed
if presence_penalty is not None:
params["presence_penalty"] = presence_penalty
if frequency_penalty is not None:

View file

@ -46,8 +46,16 @@
}
}
/* Add a tiny statusstyle section */
.status-ok { color: #006400; font-weight: bold; } /* dark green */
.status-error{ color: #8B0000; font-weight: bold; } /* dark red */
.status-ok { color: #006400; font-weight: bold; } /* dark green */
.status-error{ color: #8B0000; font-weight: bold; } /* dark red */
.copy-link {
font-size:0.9em;
margin-left:0.5em;
color:#0066cc;
cursor:pointer;
text-decoration:underline;
}
.copy-link:hover { text-decoration:none; }
</style>
</head>
<body>
@ -130,9 +138,42 @@ async function loadTags(){
try{
const data = await fetchJSON('/api/tags');
const body = document.getElementById('tags-body');
body.innerHTML = data.models.map(m=>`<tr><td class="model">${m.id || m.name}</td><td>${m.digest}</td></tr>`).join('');
const countSpan = document.getElementById('tags-count');
body.innerHTML = data.models.map(m => {
// Build the model cell
let modelCell = `${m.id || m.name}`;
// Add the copy link *only if a digest exists*
if (m.digest) {
modelCell += `
<a href="#" class="copy-link" data-source="${m.name}">
copy
</a>`;
}
return `
<tr>
<td class="model">${modelCell}</td>
<td>${m.digest || ''}</td>
</tr>`;
}).join(''); const countSpan = document.getElementById('tags-count');
countSpan.textContent = `${data.models.length}`;
// Attach copylink handlers
document.querySelectorAll('.copy-link').forEach(link => {
link.addEventListener('click', async (e) => {
e.preventDefault();
const source = link.dataset.source;
const dest = prompt(`Enter destination for ${source}:`);
if (!dest) return; // cancel if empty
try{
const resp = await fetch(`/api/copy?source=${encodeURIComponent(source)}&destination=${encodeURIComponent(dest)}`);
if (!resp.ok) throw new Error(`Copy failed: ${resp.status}`);
alert(`Copied ${source} to ${dest} successfully.`);
}catch(err){
console.error(err);
alert(`Error copying ${source} to ${dest}: ${err}`);
}
});
});
}catch(e){ console.error(e); }
}