let storage = new Storage();
class SearchBar extends React.Component {
constructor(props) {
super(props);
this.handleInputChange = this.handleInputChange.bind(this);
this.handleSnapshotToggle = this.handleSnapshotToggle.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleInputChange(event) {
this.props.onSearchInput(event.target.value);
}
handleSnapshotToggle(event) {
this.props.onSnapshotToggle(event);
}
handleSubmit(event) {
this.props.onSearchSubmit(event);
}
render() {
let btn = (
Generate
);
if (this.props != null && this.props.appName != null) {
btn = ;
}
return (
{btn}
);
}
}
class DeleteAppButton extends React.Component {
render() {
return (
Delete {this.props.appName}
);
}
}
class Message extends React.Component {
render() {
var variants = {
status: "info",
progress: "info",
failure: "danger",
done: "success",
};
switch (this.props.action) {
case "done":
case "failure":
case "status":
return (
{this.props.message}
);
case "progress":
return (
{this.props.message}
);
case "link":
return (
{this.props.message}
);
case "error":
return (
{this.props.message}
);
default:
return ;
}
}
}
class ResourceQuotaStatus extends React.Component {
render() {
if (this.props === null || this.props.resourceQuota === null) {
return ;
}
let used = this.props.resourceQuota.used;
let hard = this.props.resourceQuota.hard;
if (typeof used == "undefined" || typeof hard == "undefined") {
return ;
}
return (
);
}
}
class LogDisplay extends React.Component {
render() {
if (!this.props.logContent) {
return ;
}
return (
{this.props.logContent}
);
}
}
class Status extends React.Component {
render() {
if (this.props.messages.length == 0) {
return ;
} else {
return (
{this.props.messages.map((item) => (
))}
);
}
}
}
class AppsList extends React.Component {
render() {
let appCount = Object.keys(this.props.apps).length;
if (appCount === 0) {
return false;
}
let header = Currently running Prometheus instances
;
let apps = Object.keys(this.props.apps).map((k) => {
return (
{k}
{
this.props.onDeleteApp(k);
}}
appName={k}
/>
);
});
return (
{header}
{apps}
);
}
}
class SearchForm extends React.Component {
constructor(props) {
super(props);
this.state = {
querySearch: "",
searchInput: "",
snapshotToggle: false,
messages: [],
logContent: "",
appName: null,
apps: storage.getData(),
ws: null,
resourceQuota: {
used: 0,
hard: 0,
},
};
this.handleSearchInput = this.handleSearchInput.bind(this);
this.handleSnapshotToggle = this.handleSnapshotToggle.bind(this);
this.handleSearchSubmit = this.handleSearchSubmit.bind(this);
this.handleDeleteAppInternal = this.handleDeleteAppInternal.bind(this);
this.handleDeleteApp = this.handleDeleteApp.bind(this);
this.handleDeleteCurrentApp = this.handleDeleteCurrentApp.bind(this);
this.addMessage = this.addMessage.bind(this);
this.sendWSMessage = this.sendWSMessage.bind(this);
this.connect = this.connect.bind(this);
this.check = this.check.bind(this);
this.search = this.search.bind(this);
}
handleSearchInput(searchInput) {
this.setState({ searchInput: searchInput });
}
handleSnapshotToggle(_event) {
this.setState({ snapshotToggle: !this.state.snapshotToggle });
}
handleSearchSubmit(event) {
event.preventDefault();
let query = this.state.searchInput;
if (query.length == 0) {
return;
}
try {
let url = new URL(query);
if (this.state.snapshotToggle) {
url.searchParams.append("altsnap", "true");
}
this.search(url.toString());
} catch (e) {
console.log(e);
}
}
sendWSMessage(message) {
// add messages to queue if connection is not ready
if (!this.state.ws || this.state.ws.readyState != WebSocket.OPEN) {
if (this.state.ws) {
console.log("ws.readyState " + this.state.ws.readyState);
}
if (!this.ws_msgs) this.ws_msgs = [];
console.log("Added message " + message + " to queue");
this.ws_msgs.push(message);
} else {
console.log("Sending message " + message);
this.state.ws.send(message);
}
}
search(input) {
try {
this.state.messages = [];
this.sendWSMessage(JSON.stringify({ action: "new", message: input }));
} catch (error) {
console.log(error);
}
}
handleDeleteApp(appName) {
console.log(appName);
if (this.state.appName === appName) {
this.handleDeleteCurrentApp();
} else {
this.handleDeleteAppInternal(appName);
}
}
handleDeleteCurrentApp() {
this.handleDeleteAppInternal(this.state.appName);
// Remove message with app-label from the list
let newMessages = this.state.messages.slice(1, this.state.messages.length);
this.setState((_state) => ({
messages: newMessages,
appName: null,
}));
}
handleDeleteAppInternal(appName) {
try {
this.sendWSMessage(JSON.stringify({ action: "delete", message: appName }));
storage.removeInstance(appName);
this.setState((_state) => ({
apps: storage.getData(),
}));
} catch (error) {
console.log(error);
}
}
addMessage(message) {
if (message.action === "log") {
this.setState((state) => ({
logContent: state.logContent + message.message + "\n",
}));
return;
}
this.setState((state) => ({ messages: [...state.messages, message] }));
if (message.action === "app-label") {
this.setState((_state) => ({ appName: message.message, logContent: "" }));
}
if (message.action === "done" || message.action === "error" || message.action === "failure") {
// Remove message with progress from the list
let newMessages = this.state.messages.filter(function (message) {
return message.action != "progress";
});
this.setState((_state) => ({ messages: newMessages, logContent: "" }));
if (message.data != null) {
storage.addInstance(message.data.hash, message.data.url);
}
this.setState((_state) => ({ apps: storage.getData() }));
}
if (message.action === "rquota") {
let rquotaStatus = JSON.parse(message.message);
this.setState((_state) => ({
resourceQuota: {
used: rquotaStatus.used,
hard: rquotaStatus.hard,
},
}));
}
}
check() {
const { ws } = this.state;
if (!ws || ws.readyState == WebSocket.CLOSED) this.connect(); //check if websocket instance is closed, if so call `connect` function.
}
connect() {
var loc = window.location;
var ws_uri;
if (loc.protocol === "https:") {
ws_uri = "wss:";
} else {
ws_uri = "ws:";
}
ws_uri += "//" + loc.host;
ws_uri += "/ws/status";
var ws = new WebSocket(ws_uri);
let that = this;
var connectInterval;
const maxReconnectDelay = 10; // 10 seconds
const timeoutInSeconds = (that.timeout * 2) / 1000;
const reconnectDelay = Math.min(maxReconnectDelay, timeoutInSeconds);
// websocket onopen event listener
ws.onopen = () => {
console.log("websocket connected");
that.setState({ ws: ws });
that.timeout = 250; // reset timer to 250 on open of websocket connection
clearTimeout(connectInterval); // clear Interval on on open of websocket connection
// Send messages if there's a queue
ws.send(JSON.stringify({ action: "connect" }));
while (that.ws_msgs && that.ws_msgs.length > 0) {
ws.send(that.ws_msgs.pop());
}
};
// websocket onclose event listener
ws.onclose = (_e) => {
console.log(`Socket is closed. Reconnect will be attempted in ${reconnectDelay} seconds.`);
that.timeout = that.timeout + that.timeout; //increment retry interval
connectInterval = setTimeout(this.check, Math.min(10000, that.timeout)); //call check function after timeout
};
// websocket onerror event listener
ws.onerror = (err) => {
console.error("Socket encountered error: ", err.message, "Closing socket");
ws.close();
};
ws.onmessage = (evt) => {
console.log("Received " + evt.data);
const message = JSON.parse(evt.data);
this.addMessage(message);
};
this.setState({ ws: ws });
}
componentDidMount() {
window.addEventListener("beforeunload", this.onUnload);
this.check();
this.timeout = 0;
if (!this.state.searchInput) {
let params = new URL(window.location).searchParams;
let searchInput = params.get("search");
if (searchInput && searchInput != this.state.querySearch) {
this.state.querySearch = searchInput;
this.handleSearchInput(searchInput);
this.search(searchInput);
}
}
}
onUnload() {
if (this.state.ws) {
console.log("Closing socket");
this.state.ws.close();
}
}
componentWillUnmount() {
window.removeEventListener("beforeunload", this.onUnload);
}
render() {
let messages;
let searchClass;
if (this.state.appName != null) {
messages = ;
searchClass = null;
} else {
messages = [];
searchClass = "search-center";
}
return (
);
}
}
ReactDOM.render(, document.getElementById("container"));