1
0
mirror of https://github.com/gusaul/grpcox.git synced 2024-12-26 10:50:11 +00:00
grpcox/index/js/proto.js
Matias Alvin 293bb364c0 feat(descriptor): add support for local proto descriptor
Currently, grpcox depends on server reflection to get proto descriptor. It has a
significant drawback, since not every grpc server support
[server
reflection](https://github.com/grpc/grpc/blob/master/doc/server-reflection.md#known-implementations).
Using local proto files is more feasible, as every grpc server certainly have
one.

Even though using protofile should be simple enough, there's still a problem
regarding this. Some protofile use extra plugins for their proto. i.e.
gogoprotobuf is a project that does just that. The problems with plugins are
most of them require explicit import to the plugin inside of the protofile. It
will break grpcurl proto descriptor extraction. Thus, the plugin proto must be
uploaded alongside the protofile. Also, the protofile should be modified
automatically to change their import to local import.

Given that, I proposed a way for the user to upload multiple protofile to
grpcox. Then, use that to get the descriptor.

Changelog:
- Add `use local proto` checkbox in HTML client. On checked it will show upload
button and list of selected proto.
- `get-service` ajax will use POST when `use local proto` is checked. The
uploaded protofile will be the payload for the ajax request.
- Add a new route to handle POST "get-service". It will persist the uploaded
protofile to `/tmp/` directory and add protos field in the resource.
- Modify `openDescriptor` to use local proto if protos field in the resource is
available.
- Modify `openDescriptor` to return an error, as opening descriptor from local
proto may fail.
- Modify the main server so it can be shut down gracefully. This is necessary as
grpcox need to remove persisted proto right after the server is turned off.

This Pull Request will resolve #16
2020-01-31 10:27:46 +07:00

166 lines
5.6 KiB
JavaScript

let protoMap;
let showCollection;
// IIFE to setup event listener
(function () {
// add event listener on proto checkbox
// if checked the uploader and proto collection view will be displayed
// also the grpc reflection will use given proto instead of server reflection
const protoSwitch = document.getElementById("local-proto");
protoSwitch.addEventListener("change", function(event) {
const { checked } = event.target;
toggleDisplayProtoInput(checked);
});
// add event listener on upload proto file
// uploaded file extension will be checked, only those with *.proto will be processed
// add successful file to protoMap to be displayed
const protoUploader = document.getElementById("proto-file");
protoUploader.addEventListener("change", handleProtoUpload, false);
// add event listener on toggle proto collection display
// it will show / hide the proto collection
const protoCollectionToggle = document.getElementById("proto-collection-toggle");
protoCollectionToggle.addEventListener("click", toggleDisplayProtoCollection);
// init map to handle proto files
// every proto files will be unique to their name
protoMap = new Map();
// set proto collection display status to true
// by default the collection will be shown
showCollection = true;
}());
// toggle proto files display
// displaying upload button
function toggleDisplayProtoInput(show) {
const style = show ? "display: block" : "display: none";
const protoInput = document.getElementById("proto-input");
protoInput.removeAttribute("style");
protoInput.style.cssText = style;
}
// toggle proto files collection
// displaying uploaded protos collection
function toggleDisplayProtoCollection() {
const protoCollection = document.getElementsByClassName("proto-collection")[0];
protoCollection.removeAttribute("style");
const protoToggle = document.getElementById("proto-collection-toggle");
let collectionStyle = "";
let toggleText = protoToggle.innerHTML;
if (showCollection) {
collectionStyle = "display: none";
toggleText = toggleText.replace("Hide", "Show");
} else {
collectionStyle = "display: block";
toggleText = toggleText.replace("Show", "Hide");
}
protoCollection.style.cssText = collectionStyle;
protoToggle.innerHTML = toggleText;
showCollection = !showCollection;
}
// handling file upload event
// add uploaded files to protoMap to avoid duplication, on file with same name the older
// file will be replaced with the latest
// if the file isn't available before, add DOM element for UI representation
// file without *.proto won't be processed
function handleProtoUpload() {
const files = this.files;
for (const file of files) {
if (!file.name.endsWith(".proto")) {
continue;
}
if (!protoMap.has(file.name)) {
addProtoItem(file.name, file.size);
}
protoMap.set(file.name, file);
}
}
// adding proto item to proto collection view
// give visual representation to user and access to remove unwanted uploaded files
function addProtoItem(name, size) {
const protoItem = createProtoItem(name, size);
const collection = document.getElementsByClassName("proto-collection")[0];
collection.appendChild(protoItem);
}
// create dom element for proto item
// every item will be given id corresponding to it's name for easier access
function createProtoItem(name, size) {
const item = document.createElement("div");
item.classList.add("proto-item");
item.id = name;
const icon = document.createElement("img");
icon.src = "img/file.png";
icon.alt = "file icon";
icon.classList.add("proto-icon");
item.appendChild(icon);
const desc = createProtoItemDesc(name, size);
item.appendChild(desc);
return item;
}
// create dom element for proto item description
function createProtoItemDesc(name, size) {
const desc = document.createElement("div");
desc.classList.add("proto-desc");
const caption = document.createElement("span");
caption.classList.add("proto-caption");
const captionText = document.createTextNode(name.length > 15 ?
name.substring(0, 12) + "..." :
name);
caption.appendChild(captionText);
desc.appendChild(caption);
const sizeDOM = document.createElement("span");
sizeDOM.classList.add("proto-size");
const sizeText = document.createTextNode(size > 1000 ?
`${size/1000}kb` :
`${size}b`);
sizeDOM.appendChild(sizeText);
desc.appendChild(sizeDOM);
const remove = document.createElement("button");
remove.classList.add("btn", "btn-sm", "proto-remove");
remove.addEventListener("click", function() {removeProtoItem(name);});
const removeIcon = document.createElement("i");
removeIcon.classList.add("fa", "fa-trash");
remove.appendChild(removeIcon);
const removeText = document.createTextNode(" remove");
remove.appendChild(removeText);
desc.appendChild(remove);
return desc;
}
// remove proto item based on it's ID
function removeProtoItem(name) {
const item = document.getElementById(name);
item.parentNode.removeChild(item);
protoMap.delete(name);
}
// fetch all proto from protoMap
// compose it into formData to make it easier to send via ajax
function getProtos() {
const formData = new FormData();
for (const proto of protoMap.values()) {
formData.append("protos", proto, proto.name);
}
return formData;
}