395 lines
15 KiB
JavaScript
395 lines
15 KiB
JavaScript
function selectGrpcSource(source) {
|
|
grpcSource = source;
|
|
document.querySelectorAll('.grpc-source-btn').forEach(function(btn) {
|
|
btn.classList.toggle('active', btn.dataset.source === source);
|
|
});
|
|
document.getElementById('grpc-src-proto').hidden = source !== 'proto';
|
|
document.getElementById('grpc-src-reflection').hidden = source !== 'reflection';
|
|
document.getElementById('grpc-src-manual').hidden = source !== 'manual';
|
|
var detail = document.getElementById('grpc-method-detail');
|
|
if (detail) detail.hidden = true;
|
|
selectedRpcMethod = null;
|
|
}
|
|
|
|
function extractBlocks(text, keyword) {
|
|
var blocks = [];
|
|
var re = new RegExp(keyword + '\\s+(\\w+)\\s*\\{', 'g');
|
|
var match;
|
|
while ((match = re.exec(text)) !== null) {
|
|
var name = match[1];
|
|
var start = match.index + match[0].length;
|
|
var depth = 1;
|
|
var i = start;
|
|
while (i < text.length && depth > 0) {
|
|
if (text[i] === '{') depth++;
|
|
else if (text[i] === '}') depth--;
|
|
i++;
|
|
}
|
|
blocks.push({ name: name, body: text.slice(start, i - 1), start: match.index });
|
|
}
|
|
return blocks;
|
|
}
|
|
|
|
function stripProtoComments(text) {
|
|
text = text.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
text = text.replace(/\/\/[^\n]*/g, '');
|
|
return text;
|
|
}
|
|
|
|
function parseProto(text) {
|
|
text = stripProtoComments(text);
|
|
|
|
var pkg = '';
|
|
var pkgMatch = text.match(/\bpackage\s+([\w.]+)\s*;/);
|
|
if (pkgMatch) pkg = pkgMatch[1];
|
|
|
|
var messages = {};
|
|
extractBlocks(text, 'message').forEach(function(blk) {
|
|
var fields = [];
|
|
var fieldRe = /\b(repeated\s+)?([\w.]+)\s+(\w+)\s*=\s*\d+/g;
|
|
var fm;
|
|
while ((fm = fieldRe.exec(blk.body)) !== null) {
|
|
if (['option', 'reserved', 'extensions', 'oneof'].indexOf(fm[2]) !== -1) continue;
|
|
fields.push({ repeated: !!fm[1], type: fm[2], name: fm[3] });
|
|
}
|
|
messages[blk.name] = fields;
|
|
});
|
|
|
|
var streamingCount = 0;
|
|
var services = [];
|
|
|
|
extractBlocks(text, 'service').forEach(function(svc) {
|
|
var methods = [];
|
|
var rpcRe = /\brpc\s+(\w+)\s*\(\s*(stream\s+)?([\w.]+)\s*\)\s*returns\s*\(\s*(stream\s+)?([\w.]+)\s*\)/g;
|
|
var rm;
|
|
while ((rm = rpcRe.exec(svc.body)) !== null) {
|
|
var streamIn = !!rm[2];
|
|
var streamOut = !!rm[4];
|
|
if (streamIn || streamOut) {
|
|
streamingCount += 1;
|
|
} else {
|
|
var reqType = rm[3].split('.').pop();
|
|
var resType = rm[5].split('.').pop();
|
|
methods.push({ name: rm[1], request: reqType, response: resType });
|
|
}
|
|
}
|
|
if (methods.length > 0) {
|
|
services.push({ name: svc.name, methods: methods });
|
|
}
|
|
});
|
|
|
|
return { package: pkg, services: services, messages: messages, streamingCount: streamingCount };
|
|
}
|
|
|
|
function renderServiceResults(parsed, ids) {
|
|
protoParsed = parsed;
|
|
selectedRpcMethod = null;
|
|
|
|
var notice = document.getElementById(ids.notice);
|
|
var countEl = document.getElementById(ids.count);
|
|
if (notice) {
|
|
notice.hidden = parsed.streamingCount <= 0;
|
|
if (countEl && parsed.streamingCount > 0) countEl.textContent = parsed.streamingCount;
|
|
}
|
|
|
|
var totalMethods = parsed.services.reduce(function(n, s) { return n + s.methods.length; }, 0);
|
|
var summaryEl = document.getElementById(ids.summary);
|
|
if (summaryEl) {
|
|
var servicesLabel = window.tPlural('wizard.grpc.services_label', parsed.services.length, {
|
|
count: parsed.services.length,
|
|
});
|
|
var methodsLabel = window.tPlural('wizard.grpc.methods_label', totalMethods, {
|
|
count: totalMethods,
|
|
});
|
|
summaryEl.textContent = tfKey('wizard.grpc.services_found', {
|
|
services_label: servicesLabel,
|
|
methods_label: methodsLabel,
|
|
});
|
|
}
|
|
|
|
var listEl = document.getElementById(ids.list);
|
|
if (!listEl) return;
|
|
|
|
listEl.innerHTML = '';
|
|
if (parsed.services.length === 0) {
|
|
var emptyDiv = document.createElement('div');
|
|
emptyDiv.className = 'proto-empty';
|
|
emptyDiv.textContent = tKey('wizard.grpc.no_methods');
|
|
listEl.appendChild(emptyDiv);
|
|
} else {
|
|
var svcTmpl = document.getElementById('tmpl-proto-service-group');
|
|
var methodTmpl = document.getElementById('tmpl-proto-method-card');
|
|
parsed.services.forEach(function(svc) {
|
|
var svcNode = svcTmpl.content.cloneNode(true);
|
|
svcNode.querySelector('.proto-service-name').textContent = svc.name;
|
|
var grid = svcNode.querySelector('.proto-method-grid');
|
|
svc.methods.forEach(function(m) {
|
|
var mNode = methodTmpl.content.cloneNode(true);
|
|
var btn = mNode.querySelector('.proto-method-card');
|
|
btn.dataset.service = svc.name;
|
|
btn.dataset.method = m.name;
|
|
btn.dataset.req = m.request;
|
|
btn.dataset.res = m.response;
|
|
mNode.querySelector('.proto-method-name').textContent = m.name;
|
|
mNode.querySelector('.proto-req-type').textContent = m.request;
|
|
mNode.querySelector('.proto-res-type').textContent = m.response;
|
|
grid.appendChild(mNode);
|
|
});
|
|
listEl.appendChild(svcNode);
|
|
});
|
|
}
|
|
|
|
document.getElementById(ids.view).hidden = false;
|
|
document.getElementById('grpc-method-detail').hidden = true;
|
|
}
|
|
|
|
function renderParsedProto(parsed) {
|
|
var notice = document.getElementById('proto-streaming-text');
|
|
if (notice && parsed && parsed.streamingCount) {
|
|
notice.textContent = window.tPlural('wizard.step3.grpc.streaming_hidden', parsed.streamingCount, {
|
|
count: parsed.streamingCount,
|
|
});
|
|
}
|
|
renderServiceResults(parsed, {
|
|
notice: 'proto-streaming-notice', count: 'proto-streaming-count',
|
|
summary: 'proto-services-summary', list: 'proto-services-list', view: 'proto-parsed-view',
|
|
});
|
|
}
|
|
|
|
function renderReflectionResult(parsed) {
|
|
var reflectText = document.querySelector('#reflect-streaming-notice .info-callout-text');
|
|
if (reflectText && parsed && parsed.streamingCount) {
|
|
reflectText.textContent = window.tPlural('wizard.step3.grpc.streaming_hidden', parsed.streamingCount, {
|
|
count: parsed.streamingCount,
|
|
});
|
|
}
|
|
renderServiceResults(parsed, {
|
|
notice: 'reflect-streaming-notice', count: 'reflect-streaming-count',
|
|
summary: 'reflect-services-summary', list: 'reflect-services-list', view: 'reflect-parsed-view',
|
|
});
|
|
}
|
|
|
|
function selectRpcMethod(btn) {
|
|
document.querySelectorAll('.proto-method-card').forEach(function(b) { b.classList.remove('active'); });
|
|
btn.classList.add('active');
|
|
|
|
var svcName = btn.dataset.service;
|
|
var methodName = btn.dataset.method;
|
|
var reqType = btn.dataset.req;
|
|
var resType = btn.dataset.res;
|
|
|
|
selectedRpcMethod = { service: svcName, method: methodName, request: reqType, response: resType };
|
|
|
|
var pkg = protoParsed && protoParsed.package ? protoParsed.package + '.' : '';
|
|
var grpcPath = '/' + pkg + svcName + '/' + methodName;
|
|
|
|
var pathEl = document.getElementById('grpc-path-value');
|
|
if (pathEl) pathEl.textContent = grpcPath;
|
|
|
|
var sub = document.getElementById('grpc-method-subtitle');
|
|
if (sub) sub.textContent = 'rpc ' + methodName + ' (' + reqType + ') returns (' + resType + ')';
|
|
|
|
var reqTypeEl = document.getElementById('grpc-req-type');
|
|
var resTypeEl = document.getElementById('grpc-res-type');
|
|
if (reqTypeEl) reqTypeEl.textContent = reqType;
|
|
if (resTypeEl) resTypeEl.textContent = resType;
|
|
|
|
var sigGrid = document.getElementById('grpc-detail-sig');
|
|
if (sigGrid) sigGrid.hidden = false;
|
|
|
|
renderFieldList('grpc-req-fields', reqType);
|
|
renderFieldList('grpc-res-fields', resType);
|
|
|
|
document.getElementById('grpc-method-detail').hidden = false;
|
|
}
|
|
|
|
function renderFieldList(elId, typeName) {
|
|
var el = document.getElementById(elId);
|
|
if (!el) return;
|
|
var fields = protoParsed && protoParsed.messages[typeName];
|
|
el.innerHTML = '';
|
|
if (!fields || fields.length === 0) {
|
|
var emptyEl = document.createElement('div');
|
|
emptyEl.className = 'proto-field-empty';
|
|
emptyEl.textContent = '';
|
|
emptyEl.appendChild(document.createTextNode(tKey('wizard.grpc.fields_unresolved')));
|
|
emptyEl.appendChild(document.createElement('br'));
|
|
var hint = document.createElement('span');
|
|
hint.style.color = 'var(--text-muted)';
|
|
hint.style.fontSize = '11px';
|
|
hint.textContent = tKey('wizard.grpc.fields_unresolved_body');
|
|
emptyEl.appendChild(hint);
|
|
el.appendChild(emptyEl);
|
|
return;
|
|
}
|
|
var fieldTmpl = document.getElementById('tmpl-proto-field-row');
|
|
fields.forEach(function(f) {
|
|
var node = fieldTmpl.content.cloneNode(true);
|
|
node.querySelector('.proto-field-name').textContent = f.name;
|
|
node.querySelector('.proto-field-type').textContent = (f.repeated ? 'repeated ' : '') + f.type;
|
|
el.appendChild(node);
|
|
});
|
|
}
|
|
|
|
function handleProtoFile(event) {
|
|
var file = event.target.files[0];
|
|
if (!file) return;
|
|
var reader = new FileReader();
|
|
reader.onload = function(e) {
|
|
wizardProtoUpload = { fileName: file.name, content: e.target.result };
|
|
showProtoFileInfo(file.name, file.size);
|
|
var parsed = parseProto(e.target.result);
|
|
renderParsedProto(parsed);
|
|
};
|
|
reader.readAsText(file);
|
|
}
|
|
|
|
function handleProtoDrop(event) {
|
|
event.preventDefault();
|
|
var dropzone = document.getElementById('proto-dropzone');
|
|
if (dropzone) dropzone.classList.remove('drag-over');
|
|
var file = event.dataTransfer.files[0];
|
|
if (!file || !file.name.endsWith('.proto')) return;
|
|
var reader = new FileReader();
|
|
reader.onload = function(e) {
|
|
wizardProtoUpload = { fileName: file.name, content: e.target.result };
|
|
showProtoFileInfo(file.name, file.size);
|
|
renderParsedProto(parseProto(e.target.result));
|
|
};
|
|
reader.readAsText(file);
|
|
}
|
|
|
|
function showProtoFileInfo(name, size) {
|
|
var dropzone = document.getElementById('proto-dropzone');
|
|
var info = document.getElementById('proto-file-info');
|
|
var nameEl = document.getElementById('proto-file-name');
|
|
var sizeEl = document.getElementById('proto-file-size');
|
|
if (dropzone) dropzone.hidden = true;
|
|
if (info) info.hidden = false;
|
|
if (nameEl) nameEl.textContent = name;
|
|
if (sizeEl) sizeEl.textContent = (size / 1024).toFixed(1) + ' KB';
|
|
document.getElementById('proto-paste-area').hidden = true;
|
|
document.getElementById('proto-paste-btn').textContent = 'Paste content';
|
|
}
|
|
|
|
function resetProto() {
|
|
var dropzone = document.getElementById('proto-dropzone');
|
|
var info = document.getElementById('proto-file-info');
|
|
var parsedView = document.getElementById('proto-parsed-view');
|
|
if (dropzone) dropzone.hidden = false;
|
|
if (info) info.hidden = true;
|
|
if (parsedView) parsedView.hidden = true;
|
|
var fileInput = document.getElementById('proto-file-input');
|
|
if (fileInput) fileInput.value = '';
|
|
protoParsed = null;
|
|
selectedRpcMethod = null;
|
|
wizardProtoUpload = null;
|
|
}
|
|
|
|
function toggleProtoPaste() {
|
|
var area = document.getElementById('proto-paste-area');
|
|
var btn = document.getElementById('proto-paste-btn');
|
|
if (!area) return;
|
|
var open = !area.hidden;
|
|
area.hidden = open;
|
|
if (btn) btn.textContent = open ? 'Paste content' : 'Cancel paste';
|
|
if (!open) {
|
|
var ta = document.getElementById('proto-paste-input');
|
|
if (ta) setTimeout(function() { ta.focus(); }, 50);
|
|
}
|
|
}
|
|
|
|
function cancelProtoPaste() {
|
|
var area = document.getElementById('proto-paste-area');
|
|
var btn = document.getElementById('proto-paste-btn');
|
|
if (area) area.hidden = true;
|
|
if (btn) btn.textContent = 'Paste content';
|
|
}
|
|
|
|
function parseProtoPasted() {
|
|
var ta = document.getElementById('proto-paste-input');
|
|
if (!ta || !ta.value.trim()) return;
|
|
var parsed = parseProto(ta.value);
|
|
wizardProtoUpload = { fileName: 'pasted.proto', content: ta.value };
|
|
document.getElementById('proto-paste-area').hidden = true;
|
|
var dropzone = document.getElementById('proto-dropzone');
|
|
if (dropzone) dropzone.hidden = true;
|
|
var info = document.getElementById('proto-file-info');
|
|
var nameEl = document.getElementById('proto-file-name');
|
|
var sizeEl = document.getElementById('proto-file-size');
|
|
if (info) info.hidden = false;
|
|
if (nameEl) nameEl.textContent = 'pasted.proto';
|
|
if (sizeEl) sizeEl.textContent = (ta.value.length / 1024).toFixed(1) + ' KB';
|
|
renderParsedProto(parsed);
|
|
}
|
|
|
|
var REFLECTION_MOCK = {
|
|
package: 'acme.v1',
|
|
streamingCount: 2,
|
|
services: [
|
|
{ name: 'ContactService', methods: [
|
|
{ name: 'GetContact', request: 'GetContactRequest', response: 'Contact' },
|
|
{ name: 'CreateContact', request: 'CreateContactRequest', response: 'Contact' },
|
|
{ name: 'UpdateContact', request: 'UpdateContactRequest', response: 'Contact' },
|
|
{ name: 'DeleteContact', request: 'DeleteContactRequest', response: 'DeleteContactResponse' },
|
|
]},
|
|
{ name: 'CompanyService', methods: [
|
|
{ name: 'GetCompany', request: 'GetCompanyRequest', response: 'Company' },
|
|
{ name: 'ListCompanies', request: 'ListCompaniesRequest', response: 'ListCompaniesResponse' },
|
|
]},
|
|
],
|
|
messages: {
|
|
'GetContactRequest': [{ type: 'string', name: 'id' }],
|
|
Contact: [{ type: 'string', name: 'id' }, { type: 'string', name: 'first_name' }, { type: 'string', name: 'last_name' }, { type: 'string', name: 'email' }, { type: 'string', name: 'company_id' }, { type: 'string', name: 'created_at' }],
|
|
'CreateContactRequest': [{ type: 'string', name: 'first_name' }, { type: 'string', name: 'last_name' }, { type: 'string', name: 'email' }, { type: 'string', name: 'company_id' }],
|
|
'UpdateContactRequest': [{ type: 'string', name: 'id' }, { type: 'string', name: 'first_name' }, { type: 'string', name: 'last_name' }, { type: 'string', name: 'email' }],
|
|
'DeleteContactRequest': [{ type: 'string', name: 'id' }],
|
|
'DeleteContactResponse': [{ type: 'bool', name: 'success' }],
|
|
'GetCompanyRequest': [{ type: 'string', name: 'id' }],
|
|
Company: [{ type: 'string', name: 'id' }, { type: 'string', name: 'name' }, { type: 'string', name: 'domain' }, { type: 'int32', name: 'employee_count' }],
|
|
'ListCompaniesRequest': [{ type: 'int32', name: 'page' }, { type: 'int32', name: 'page_size' }, { type: 'string', name: 'query' }],
|
|
'ListCompaniesResponse': [{ type: 'Company', name: 'companies', repeated: true }, { type: 'int32', name: 'total' }],
|
|
},
|
|
};
|
|
|
|
function startReflection() {
|
|
goToStep(5);
|
|
showWizardLiveStatus(
|
|
tKey('wizard.grpc.discovery_title'),
|
|
tKey('wizard.grpc.discovery_body')
|
|
);
|
|
}
|
|
|
|
function grpcManualRouteInput(val) {
|
|
var detail = document.getElementById('grpc-method-detail');
|
|
var sigGrid = document.getElementById('grpc-detail-sig');
|
|
if (!val || !val.startsWith('/') || val.indexOf('/', 1) === -1) {
|
|
if (detail) detail.hidden = true;
|
|
return;
|
|
}
|
|
var parts = val.replace(/^\//, '').split('/');
|
|
var servicePart = parts[0] || '';
|
|
var methodName = parts[1] || '';
|
|
var serviceName = servicePart.split('.').pop();
|
|
|
|
var pathEl = document.getElementById('grpc-path-value');
|
|
if (pathEl) pathEl.textContent = val;
|
|
|
|
var sub = document.getElementById('grpc-method-subtitle');
|
|
if (sub) sub.textContent = serviceName + '/' + methodName;
|
|
|
|
if (sigGrid) sigGrid.hidden = true;
|
|
|
|
grpcManualTypeInput();
|
|
if (detail) detail.hidden = false;
|
|
}
|
|
|
|
function grpcManualTypeInput() {
|
|
var reqInput = document.getElementById('grpc-manual-req-type');
|
|
var resInput = document.getElementById('grpc-manual-res-type');
|
|
var reqTypeEl = document.getElementById('grpc-req-type');
|
|
var resTypeEl = document.getElementById('grpc-res-type');
|
|
if (reqTypeEl) reqTypeEl.textContent = (reqInput && reqInput.value.trim()) || '—';
|
|
if (resTypeEl) resTypeEl.textContent = (resInput && resInput.value.trim()) || '—';
|
|
}
|