All files / src/plugin panel.ts

100% Statements 5/5
100% Branches 0/0
100% Functions 1/1
100% Lines 5/5

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 371x 1x 1x         1x 2x                                                        
export const CONNECTION_PANEL_NAMESPACE = 'ui://gitlab-mcp-local/';
export const CONNECTION_PANEL_URI = CONNECTION_PANEL_NAMESPACE + 'connection.html';
export const CONNECTION_PANEL_META = {
  ui: { csp: { connectDomains: [], resourceDomains: [], frameDomains: [] }, prefersBorder: true },
};
 
/** Credentials travel through the host's app bridge, never through conversation text. */
export function connectionPanel(): string {
  return String.raw`<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<style>
:root{color-scheme:light dark;font:14px/1.5 var(--font-sans,system-ui);background:var(--color-background-primary,Canvas);color:var(--color-text-primary,CanvasText)}
*{box-sizing:border-box}body{margin:0;padding:24px;max-width:660px}h1{font-size:23px;margin:0}p{color:var(--color-text-secondary,GrayText)}
label{display:block;margin:18px 0 5px;font-weight:600}input,select,button{font:inherit;border:1px solid var(--color-border-primary,GrayText);border-radius:8px;padding:10px;background:var(--color-background-secondary,Canvas);color:inherit}input,select{width:100%}button{cursor:pointer}button:disabled{opacity:.5;cursor:wait}.actions{display:flex;gap:10px;flex-wrap:wrap;margin-top:22px}.primary{background:#6b4fbb;color:white;border-color:#6b4fbb}:focus-visible{outline:3px solid #b197fc;outline-offset:2px}#status{padding:12px;border:1px solid var(--color-border-primary,GrayText);border-radius:10px;margin:20px 0;white-space:pre-wrap}small{color:var(--color-text-secondary,GrayText)}[hidden]{display:none!important}
</style><h1>Your GitLab connection</h1><p>Choose where GitLab runs. Change this later without reinstalling the plugin.</p>
<div id="status" role="status" aria-live="polite">Loading connection...</div>
<form id="form"><label for="mode">Connect to</label><select id="mode"><option value="local">GitLab instance, run MCP locally</option><option value="remote">Existing GitLab MCP server</option></select>
<label for="url" id="address-label">GitLab address</label><input id="url" type="url" required placeholder="https://gitlab.example.com" autocomplete="url"><small id="hint">The core server starts automatically. GitLab 16.0 or later.</small>
<div id="remote-auth" hidden><label for="authentication">Authentication</label><select id="authentication"><option value="oauth">Browser sign-in (OAuth)</option><option value="bearer">Bearer token</option><option value="none">No authentication</option></select></div>
<div id="secret"><label for="token" id="token-label">GitLab personal access token</label><input id="token" type="password" autocomplete="off" spellcheck="false"><small>Stored privately on this computer. Never paste credentials into the chat.</small></div>
<details id="advanced"><summary>Local connection options</summary><label for="init-timeout">Initialization budget (milliseconds)</label><input id="init-timeout" type="number" min="1000" max="45000" step="1000" placeholder="Core default: 5000"><small>For slow instances, increase the initial discovery budget. Maximum 45000 leaves time for the host to receive the result.</small></details>
<div class="actions"><button class="primary" type="submit">Verify and save</button><button type="button" id="reconnect">Reconnect saved connection</button><button type="button" id="cancel">Cancel setup</button><button type="button" id="disconnect">Disconnect</button><button type="button" id="refresh">Refresh status</button><button type="button" id="authorize" hidden>Continue browser sign-in</button></div></form>
<script>
(()=>{'use strict';const el=id=>document.getElementById(id);let next=0,revision=0,authorizationUrl=null,loaded=false;const pending=new Map();
function request(method,params){return new Promise((resolve,reject)=>{const id=++next;const timer=setTimeout(()=>{pending.delete(id);reject(Error('The host did not answer. Refresh status before retrying.'));},60000);pending.set(id,{resolve,reject,timer});window.parent.postMessage({jsonrpc:'2.0',id,method,params},'*');});}
function applyContext(context){if(!context)return;if(context.theme==='dark'||context.theme==='light')document.documentElement.style.colorScheme=context.theme;const variables=context.styles&&context.styles.variables;if(variables)for(const [name,value] of Object.entries(variables))if(name.startsWith('--')&&typeof value==='string')document.documentElement.style.setProperty(name,value);}
window.addEventListener('message',event=>{if(event.source!==window.parent)return;const m=event.data;if(!m||m.jsonrpc!=='2.0')return;const p=pending.get(m.id);if(p){clearTimeout(p.timer);pending.delete(m.id);m.error?p.reject(Error(m.error.message)):p.resolve(m.result);}if(m.method==='ui/notifications/host-context-changed')applyContext(m.params);if(m.method==='ui/resource-teardown'&&m.id!==undefined){window.parent.postMessage({jsonrpc:'2.0',id:m.id,result:{}},'*');}});
async function call(action,extra={}){const r=await request('tools/call',{name:'gitlab_plugin_connection',arguments:{action,revision,...extra}});if(r.isError)throw Error('Could not change the connection. Refresh status and try again.');return r.structuredContent;}
function render(s){revision=s.revision;authorizationUrl=s.authorizationUrl;el('authorize').hidden=!authorizationUrl;if(!loaded&&s.url&&s.mode){el('mode').value=s.mode;el('url').value=s.url;if(s.authentication)el('authentication').value=s.authentication;if(s.initTimeoutMs)el('init-timeout').value=String(s.initTimeoutMs);fields();}loaded=true;el('status').textContent=[s.state,s.mode==='local'?'Local GitLab':s.mode==='remote'?'Existing MCP server':null,s.url,s.proposedTarget?'Connecting to '+s.proposedTarget.url:null,s.message,s.identity?JSON.stringify(s.identity,null,2):null].filter(Boolean).join('\n');}
function fields(){const local=el('mode').value==='local';el('advanced').hidden=!local;el('remote-auth').hidden=local;el('secret').hidden=!local&&el('authentication').value!=='bearer';el('address-label').textContent=local?'GitLab address':'MCP endpoint';el('token-label').textContent=local?'GitLab personal access token':'MCP bearer token';el('hint').textContent=local?'GitLab 16.0 or later. Use a token with the permissions you need.':'Use the server endpoint, usually ending in /mcp. Browser sign-in requires OAuth on that server.';}
async function run(action,extra){try{render(await call(action,extra));}catch(e){el('status').textContent=e.message;}}
el('mode').onchange=fields;el('authentication').onchange=fields;el('form').onsubmit=async event=>{event.preventDefault();const input={mode:el('mode').value,url:el('url').value};if(input.mode==='remote')input.authentication=el('authentication').value;if(input.mode==='local'&&el('init-timeout').value!=='')input.initTimeoutMs=Number(el('init-timeout').value);if(!el('secret').hidden)input.token=el('token').value;el('token').value='';await run('connect',{connection:input});};
for(const action of ['reconnect','cancel','disconnect','refresh'])el(action).onclick=()=>run(action==='refresh'?'status':action);
el('authorize').onclick=()=>{if(authorizationUrl)request('ui/open-link',{url:authorizationUrl}).catch(e=>{el('status').textContent=e.message;});};
request('ui/initialize',{protocolVersion:'2026-01-26',appInfo:{name:'GitLab connection',version:'1.0.0'},appCapabilities:{}}).then(result=>{applyContext(result.hostContext);window.parent.postMessage({jsonrpc:'2.0',method:'ui/notifications/initialized',params:{}},'*');return run('status');}).catch(e=>{el('status').textContent=e.message;});fields();})();
</script></html>`;
}