
    acjcG                        d dl mZmZmZmZ d dlZd dlZ ee      Zddddddddd	d
dddddddd	dddddddddddddddddddddd d!dddd	d"d#d$d%d&d"d'd(d)d*d+d,d-d)dd(d.d.d/d0d1d.dd(d2d3d4d5d6d7d8d(d9d:d;d<d=d9d>d(d?d@dAdBdCdddd	dDdEdFdGdHdIdJd(dKdLdMdNdOdKddPd	gZ	dQ Z
dR ZdSZej                  dT      dU        Zej                  dV      dW        Zej                  dXdYgZ      d[        Zd\Zd]d^d_d`dadbd]dcddd`dadbd]dedfd`dadbdgdhdidjdkdbdgdldmdjdkdbdgdndodjdkdbdgdpdqdjdkdbgZdr Zds Zej                  dtdugZ      dv        Zej                  dtdYgZ      dw        Zej                  dxdYgZ      dy        Zedzk(  rej1                  d{d|dP}       yy)~    )Flaskjsonifyrequestrender_template_stringNvpshostzVPS HostznHost-Kontrolle: Commander, Docker, systemd, Logs, IONOS, n8n, LiteLLM, Portainer, Explorer, Security, Backups.u   🖥z+https://vpshost.agentsolutions-mallorca.comT)idnamedesciconurl	containercomposeexternalz
hermes-vpsz
Hermes VPSz]Zentrale Hermes-VPS-Rolle. Oeffnet das zentrale Profil unter /opt/hermes-commander-next-data.u   ⚡z/https://hermes-root.agentsolutions-mallorca.comzhermes-carlou   Hermes — CarlozCHermes-VPS-Profil Carlo. Oeffnet HERMES_HOME /var/lib/hermes/carlo.z0https://hermes-carlo.agentsolutions-mallorca.comzIhttps://hermes-carlo.agentsolutions-mallorca.com/personas/?instance=carloz/opt/struktur/hermes-carlo)r   r	   r
   r   r   r   persona_urlr   zhermes-mariu   Hermes — MarizAHermes-VPS-Profil Mari. Oeffnet HERMES_HOME /var/lib/hermes/mari.z/https://hermes-mari.agentsolutions-mallorca.comzhermes-mari-workspacezGhttps://hermes-mari.agentsolutions-mallorca.com/personas/?instance=mariz#/opt/struktur/hermes-workspace-mariionoszIONOS Cloud Panelz*VPS Verwaltung, DNS, Server-Einstellungen.u   🖥️zhttps://my.ionos.demirofishMiroFishzCMulti-Agenten-Simulation zur Zukunftsvorhersage und Marktforschung.u   🐟z,https://mirofish.agentsolutions-mallorca.comz/opt/struktur/mirofish)r   r	   r
   r   r   r   r   explorerExplorerz>Dateizugriff auf alle VPS-Verzeichnisse und Dateien. (INAKTIV)u   📁z,https://explorer.agentsolutions-mallorca.comn8nz>Workflow-Automatisierung. Verbindet Apps, APIs und KI-Agenten.u   🔗z'https://n8n.agentsolutions-mallorca.comgraphitiGraphitizLZentrales Wissensgehirn. Knowledge Graph fuer alle Agenten und Datenquellen.u   🕸️z1https://graphiti.agentsolutions-mallorca.com/docszgraphiti-servicez/opt/struktur/graphitilitellmzLiteLLM Gatewayz_KI-Router: Free-Modelle zuerst, Paid als Fallback. Kostenkontrolle fuer alle Agenten. (INAKTIV)u   🔀z.https://litellm.agentsolutions-mallorca.com/uiz/opt/struktur/litellmairtableu   Airtable — DatenbankzAIngest Service Datenbank. Kunden, Projekte und analysierte Daten.u   🗄️z&https://airtable.com/appAlQPqFgQwjFnTu	paperclip	Paperclipz:Agent-Orchestrator. CEO-Agent mit DeepSeek via OpenRouter.u   📎z-https://paperclip.agentsolutions-mallorca.comzdocker-paperclip-1z/opt/struktur/paperclip/docker	portainer	Portainerz/Docker Container Management. Start, Stop, Logs.u   🐳zhttp://127.0.0.1:9000Fc                     	 t        j                  dddd| gddd      }|j                  j                         }|dk(  ry	|d
k(  ry|xs dS #  Y yxY w)Ndockerinspectz--formatz{{.State.Status}}T   capture_outputtexttimeoutrunningonlineexitedoffline	unbekannt)
subprocessrunstdoutstrip)r   resultstatuss      /opt/struktur/dashboard/app.py
get_statusr3   {   sj    y*.A9MdA
 $$&Yx([(s   =A  A A Ac                     | d   rC| d   }|dk(  rd| d}n|dk(  rd| d}n|dk(  rd| d}t        j                  d	d	d	d
      }nt        j                  d|| d   gd	d	d      }|j                  dk(  S )Nr   startzcd z && docker compose up -dstopz && docker compose stoprestartz && docker compose restartT<   )shellr$   r%   r&   r    r      r#   r   )r,   r-   
returncode)serviceactioncompose_dircmdr0   s        r2   docker_actionr@      s    yi(W}$<=Cv}$;<Cy }$>?C44Y[\vw{34dB
 !!    uA  <!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Agent Solutions Workspace</title>
<style>
  * { margin: 0; padding: 0; box-sizing: border-box; }
  body { background: #0f1117; color: #e2e8f0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; min-height: 100vh; }
  header { padding: 2rem 3rem; border-bottom: 1px solid #1e2330; }
  header h1 { font-size: 1.5rem; font-weight: 600; color: #fff; }
  header span { font-size: 0.85rem; color: #64748b; }
  .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 1.5rem; padding: 3rem; }
  .card { background: #161b27; border: 1px solid #1e2330; border-radius: 12px; padding: 1.5rem; }
  .card-header { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 1rem; }
  .card-icon { font-size: 2rem; }
  .badge { font-size: 0.7rem; padding: 0.25rem 0.7rem; border-radius: 99px; font-weight: 600; }
  .badge.online { background: #1a3a2a; color: #4ade80; border: 1px solid #166534; }
  .badge.offline { background: #3a1a1a; color: #f87171; border: 1px solid #7f1d1d; }
  .badge.loading { background: #2a2a1a; color: #fbbf24; border: 1px solid #78350f; }
  .badge.planned { background: #1a2a3a; color: #93c5fd; border: 1px solid #1e3a5f; }
  .card-title { font-size: 1.1rem; font-weight: 600; color: #fff; margin-bottom: 0.4rem; }
  .card-desc { font-size: 0.85rem; color: #64748b; line-height: 1.5; margin-bottom: 1.2rem; }
  .card-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; }
  .btn { font-size: 0.75rem; padding: 0.4rem 0.9rem; border-radius: 6px; border: none; cursor: pointer; font-weight: 500; transition: all 0.15s; }
  .btn-open { background: #1e3a5f; color: #60a5fa; }
  .btn-open:hover { background: #1e4a7f; }
  .btn-restart { background: #1e3a2a; color: #4ade80; }
  .btn-restart:hover { background: #1e5a3a; }
  .btn-stop { background: #3a1e1e; color: #f87171; }
  .btn-stop:hover { background: #5a1e1e; }
  .btn-start { background: #1a3a2a; color: #4ade80; }
  .btn-start:hover { background: #1a5a3a; }
  .btn:disabled { opacity: 0.4; cursor: not-allowed; }

  .modal-overlay { display:none; position:fixed; inset:0; background:rgba(0,0,0,0.7); z-index:1000; align-items:center; justify-content:center; }
  .modal-overlay.visible { display:flex; }
  .modal { background:#1e2330; border:1px solid #2d3748; border-radius:14px; padding:2rem; min-width:420px; max-width:95vw; }
  .modal h2 { font-size:1.1rem; font-weight:700; color:#fff; margin-bottom:1.2rem; }
  .modal select { width:100%; padding:0.5rem 0.75rem; background:#0f1117; border:1px solid #2d3748; border-radius:7px; color:#e2e8f0; font-size:0.9rem; margin-bottom:1rem; }
  .modal-actions { display:flex; gap:0.75rem; justify-content:flex-end; margin-top:0.5rem; }
  .btn-cancel { background:#1e2330; border:1px solid #2d3748; color:#94a3b8; padding:0.45rem 1.1rem; border-radius:7px; cursor:pointer; font-size:0.85rem; }
  .btn-save { background:#1e3a5f; color:#60a5fa; border:none; padding:0.45rem 1.1rem; border-radius:7px; cursor:pointer; font-size:0.85rem; font-weight:600; }
  .current-model { font-size:0.8rem; color:#64748b; margin-bottom:1rem; }

  footer { text-align: center; padding: 2rem; color: #334155; font-size: 0.8rem; }
</style>
</head>
<body>
<header style="display:flex; align-items:center; justify-content:space-between;">
  <div>
    <h1>Agent Solutions Workspace</h1>
    <span>Werkzeuge & Dienste — VPS 31.70.68.228</span>
  </div>
  <a href="https://agentsolutions-mallorca.com" style="font-size:0.85rem; color:#3b82f6; text-decoration:none; border:1px solid #1e3a5f; padding:0.4rem 1rem; border-radius:8px;">&#8592; Zurück</a>
</header>
<div class="grid" id="grid">
  {% for s in services %}
  <div class="card" id="card-{{s.id}}">
    <div class="card-header">
      <div class="card-icon">{{s.icon}}</div>
      <span class="badge loading" id="badge-{{s.id}}">{% if s.get('external') %}extern{% elif s.get('planned') %}geplant{% else %}...{% endif %}</span>
    </div>
    <div class="card-title">{{s.name}}</div>
    <div class="card-desc">{{s.desc}}</div>
    <div class="card-actions">
      <a href="{{s.url}}" target="_blank"><button class="btn btn-open">Öffnen</button></a>
      {% if s.get('persona_url') %}<a href="{{s.persona_url}}" target="_blank"><button class="btn" style="background:#7c3aed;color:#fff">Personas</button></a>{% endif %}
      {% if not s.get('external') %}
      <button class="btn btn-restart" onclick="action('{{s.id}}','restart')">Restart</button>
      <button class="btn btn-stop" onclick="action('{{s.id}}','stop')">Stop</button>
      <button class="btn btn-start" onclick="action('{{s.id}}','start')">Start</button>
      {% if s.id == 'hermes-carlo' %}
      <button class="btn" style="background:#3a1a1a;color:#f87171;font-weight:700;" onclick="hermesJobsReset()">&#128465; Jobs Reset</button>
      {% endif %}
      {% if s.id == 'mirofish' %}
      <button class="btn" style="background:#1e2a3a;color:#a78bfa;" onclick="openMirofishModal()">🧠 Modell</button>
      {% endif %}
      {% endif %}
    </div>
  </div>
  {% endfor %}
</div>
<footer>Agent Solutions · VPS IONOS · 31.70.68.228</footer>
<script>
function updateStatus() {
  fetch('/api/status').then(r=>r.json()).then(data => {
    data.forEach(s => {
      const b = document.getElementById('badge-'+s.id);
      b.textContent = s.status;
      b.className = 'badge ' + (s.status === 'online' ? 'online' : s.status === 'offline' ? 'offline' : s.status === 'geplant' ? 'planned' : 'loading');
    });
  });
}
function action(id, act) {
  const b = document.getElementById('badge-'+id);
  b.textContent = '...';
  b.className = 'badge loading';
  fetch('/api/action', {
    method: 'POST',
    headers: {'Content-Type':'application/json'},
    body: JSON.stringify({id, action: act})
  }).then(()=> setTimeout(updateStatus, 3000));
}
updateStatus();
setInterval(updateStatus, 15000);
</script>

<div class="modal-overlay" id="mirofish-modal">
  <div class="modal">
    <h2>🐟 MiroFish — Modell wechseln</h2>
    <div class="current-model">Aktuell: <span id="modal-current-model">...</span></div>
    <select id="modal-model-select"></select>
    <div class="modal-actions">
      <button class="btn-cancel" onclick="closeMirofishModal()">Abbrechen</button>
      <button class="btn-save" id="btn-modal-save" onclick="saveMirofishModel()">Speichern &amp; Neustart</button>
    </div>
  </div>
</div>

<script>
async function openMirofishModal() {
  document.getElementById('mirofish-modal').classList.add('visible');
  const res = await fetch('/api/mirofish/model');
  const data = await res.json();
  document.getElementById('modal-current-model').textContent = data.current;
  const sel = document.getElementById('modal-model-select');
  sel.innerHTML = data.models.map(m =>
    `<option value="${m.model}" ${m.model===data.current?'selected':''}>[${m.provider}] ${m.label}</option>`
  ).join('');
}
function closeMirofishModal() {
  document.getElementById('mirofish-modal').classList.remove('visible');
}
async function saveMirofishModel() {
  const btn = document.getElementById('btn-modal-save');
  btn.disabled = true; btn.textContent = '⏳ Neustart...';
  const model = document.getElementById('modal-model-select').value;
  const res = await fetch('/api/mirofish/model', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({model})});
  const data = await res.json();
  if (data.ok) { btn.textContent = '✅ Gespeichert'; setTimeout(closeMirofishModal, 1500); }
  else { btn.textContent = '❌ Fehler'; btn.disabled = false; }
}
</script>

</body>
</html>/c                  ,    t        t        t              S )N)services)r   HTMLSERVICES rA   r2   indexrH   0  s    !$::rA   z/api/statusc            
          t        t        D  cg c]<  } | d   | j                  d      rdn | j                  d      rt        | d         ndd> c}       S c c} w )Nr   plannedgeplantr   extern)r   r1   )r   rF   getr3   )ss    r2   r1   r1   4  sx      ]e  f  XY1T7155;Kipqpupu  wB  qCR\]^_j]kRl  IQ  S  f  g  g  fs   AAz/api/actionPOST)methodsc                      t         j                  t        fdt        D        d       } | st	        ddi      dfS t        | d         }t	        d|i      S )Nc              3   :   K   | ]  }|d    d    k(  s|  yw)r   NrG   ).0rN   datas     r2   	<genexpr>zaction.<locals>.<genexpr>;  s      A!1T7d4j+@AAs   okFi  r=   )r   jsonnextrF   r   r@   )r<   rV   rT   s     @r2   r=   r=   8  sT    <<DAxA4HGe}%s**	wX	/BD":rA   z/opt/struktur/mirofish/.envOpenAIu%   GPT-4.1-mini (empfohlen, ~3€/15Rdn)zgpt-4.1-mini-2025-04-14zhttps://api.openai.com/v1zsk-proj-VBaZD9yxd4_eAzrXGNGyvr2Tn0dM1WxUl8yhjvvETdl5B_cJUpTfuXlr32gihSJ355aHfrcUqUT3BlbkFJKt2AXBxHRy3l7HrUwfrFOeT7ku7Z5cw8HRuXd8O2umxFWKvK1g6Zn6m2i9ZPq_44ah2egN0n8A)providerlabelmodelbase_urlapi_keyu   GPT-4.1 (~15€/15Rdn)zgpt-4.1-2025-04-14u   GPT-4o (~30€/15Rdn)zgpt-4o
OpenRouteru   DeepSeek-V3 (~2€/15Rdn)zdeepseek/deepseek-chatzhttps://openrouter.ai/api/v1zIsk-or-v1-3425729d152eb4a647e1db7b0411de55d01f178f846ba37caf1465df0f3e5c8fu   DeepSeek-R1 (~5€/15Rdn)zdeepseek/deepseek-reasoneru   Qwen2.5-72B (~2€/15Rdn)zqwen/qwen2.5-72b-instructu   Kimi K2 (~2€/15Rdn)zmoonshotai/kimi-k2c                      	 t        t              5 } | D ]A  }|j                  d      s|j                         j	                  dd      d   c cd d d        S  	 d d d        y# 1 sw Y   yxY w#  Y yxY w)NzLLM_MODEL_NAME==   r+   )openMIROFISH_ENV
startswithr/   split)flines     r2   _read_mirofish_modelri   M  s{    , 	91 9??#45::<--c15a88	9 	99	9 	9 s9   A. A"$A"	A. A"A. "A+'A. +A. .A2c                    t        t              5 }|j                         }d d d        | d   | d   | d   | d   | d   d}g }D ]k  }d}|j                         D ]@  \  }}|j	                  |dz         s|j                  |dz   |z   t        d      z          d} n |r[|j                  |       m t        t        d	      5 }|j                  |       d d d        y # 1 sw Y   xY w# 1 sw Y   y xY w)
Nr^   r]   r\   )LLM_API_KEYLLM_BASE_URLLLM_MODEL_NAMEOPENAI_API_KEYOPENAI_API_BASE_URLFra   
   Tw)rc   rd   	readlinesitemsre   appendchr
writelines)		model_cfgrg   lineskeys	new_linesrh   replacedkvs	            r2   _write_mirofish_envr~   W  s   	l	 q  )3(4(1(3(4D I #JJL 	DAqq3w'  S1s2w!67		
 T"# 
lC	   A	Y   ' &   s   CC)C&)C2z/api/mirofish/modelGETc                  :    t               } t        | t        d      S )N)currentmodels)ri   r   MIROFISH_MODELS)r   s    r2   mirofish_model_getr   n  s    "$Gw/BCCrA   c                      t         j                  } | j                  d      t        fdt        D        d       }|st        ddd      dfS t        |       t        j                  ddd	
       t        dd      S )Nr\   c              3   4   K   | ]  }|d    k(  s|  yw)r\   NrG   )rS   mmodel_ids     r2   rU   z%mirofish_model_set.<locals>.<genexpr>w  s     EaajH.DEs   FzUnbekanntes ModellrV   errori  z<cd /opt/struktur/mirofish && docker compose restart mirofishTr8   )r9   r&   )rV   r\   )	r   rW   rM   rX   r   r   r~   r,   r-   )rT   cfgr   s     @r2   mirofish_model_setr   s  sp    <<Dxx H
E?Et
LCe.BCDcIINNQY]gij$233rA   z/api/hermes-resetc                  .   dd l } dd l} 	 | j                  j                  ddd      }| j                  j	                  |d      5 }t        dd	i      cd d d        S # 1 sw Y   y xY w# t        $ r#}t        d
t        |      d      dfcY d }~S d }~ww xY w)Nr   zhttp://127.0.0.1:9099/resetrO   rA   )methodrT   rp   )r&   rV   TFr   i  )urllib.requesturllib.errorr   Requesturlopenr   	Exceptionstr)urllibreqres       r2   hermes_resetr     s    '<nn$$%B6X[$\^^##C#4 	)D$<(	) 	) 	) <ec!f56;;<s;   ;A( A	A( A%!A( %A( (	B1B	BB__main__z	127.0.0.1i  )hostportdebug)flaskr   r   r   r   r,   rW   __name__apprF   r3   r@   rE   routerH   r1   r=   rd   r   ri   r~   r   r   r   r-   rG   rA   r2   <module>r      s   A A Ho j  3c  ms  |i  x|  IM  [_  `o@	 "UA#b/	 !S@,`8	 #<$	 U=+ P= P8 ^B'+ !q?* (S7	 L>)3 A& 	Ssj ""Rh 3; ; =g g =6(+ , -(OZs  D_  pV  W(@[o  E`  qW  X(?[c  E`  qW  X(C[s  Ec  p{  |(C[w  Ec  p{  |(C[v  Ec  p{  |(?[o  Ec  p{  |	 .  5'2D 3D  6(34 44 1< 2< zGG4uG5 rA   