Skip to content

Commit

Permalink
Created a two-step configuration wizard, modified navbar to change co…
Browse files Browse the repository at this point in the history
…nfiguration

Also modified the splunk-local target in Makefile to depend on the venv and disable caching locally.
Created a Rest API handler to be able to call the Flare SDK endpoints from Javascript
Created a Home page with Lorem Ipsum
  • Loading branch information
Marc-Antoine Hinse committed Oct 24, 2024
1 parent ebddaba commit 55f3616
Show file tree
Hide file tree
Showing 15 changed files with 201 additions and 41 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ venv-tools
flare_splunk_integration/local
flare_splunk_integration/metadata/local.meta
__pycache__/
.vscode/

flare_splunk_integration/bin/vendor/*
11 changes: 10 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ clean:
@rm -rf venv
@rm -rf venv-tools
@rm -rf flare_splunk_integration/bin/vendor
@unlink "/Applications/Splunk/etc/apps/flare_splunk_integration" || true
@echo "Done."

.PHONY: package
Expand Down Expand Up @@ -79,7 +80,7 @@ mypy: venv-tools
venv-tools/bin/mypy flare_splunk_integration

.PHONY: splunk-local
splunk-local:
splunk-local: venv
@echo "Create symlink from app to Splunk Enterprise"
@if [ ! -d "/Applications/Splunk/etc/apps" ]; then \
echo "Splunk Enterprise isn't installed"; \
Expand All @@ -88,3 +89,11 @@ splunk-local:

@unlink "/Applications/Splunk/etc/apps/flare_splunk_integration" || true
@ln -s "$(CURDIR)/flare_splunk_integration" "/Applications/Splunk/etc/apps/flare_splunk_integration"

rm -f "flare_splunk_integration/local/web.conf"
cp "flare_splunk_integration/default/web.conf" "flare_splunk_integration/local/web.conf"
@echo "" >> "flare_splunk_integration/local/web.conf"
@echo "" >> "flare_splunk_integration/local/web.conf"
@echo "[settings]" >> "flare_splunk_integration/local/web.conf"
@echo "cacheEntriesLimit = 0" >> "flare_splunk_integration/local/web.conf"
@echo "cacheBytesLimit = 0" >> "flare_splunk_integration/local/web.conf"
24 changes: 20 additions & 4 deletions flare_splunk_integration/README
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,26 @@ This application requires an **API key** and your **tenant ID** from https://app
1. Log into your account at https://app.flare.io/#/login.
2. Once logged in go to your Profile page, look for the "API Keys" section and create an API key.
3. Copy your API key using the provided copy icon.
4. Paste your API key in the Flare Splunk Integration configuration screen in the API Key field.
5. Again, back on the Profile page of your account, you will see a Tenants section.
6. Copy your tenant ID and paste it in the Tenant ID field on the configuration screen.
4. Paste your API key in the Flare Splunk Integration configuration screen in the API Key field and press Next Step.
5. In the next page, select the Tenant you want to ingest data from and press Submit.

# Binary File Declaration
bin/vendor/charset_normalizer/md__mypyc.cpython-39-x86_64-linux-gnu.so
bin/vendor/charset_normalizer/md.cpython-39-x86_64-linux-gnu.so
bin/vendor/charset_normalizer/md.cpython-39-x86_64-linux-gnu.so

# Development

If you have your own Splunk Enterprise installed locally, you can use

```
make splunk-local
```

To download the required dependencies, generate a symlink between the Splunk app and the Splunk Enterprise applications folder and disable the caching using a local stanza in web.conf.

When you modify files, go on these pages to refresh the cache to see your changes:

```
http://localhost:8000/debug/refresh
http://localhost:8000/_bump
```
81 changes: 62 additions & 19 deletions flare_splunk_integration/appserver/static/javascript/views/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,38 +10,81 @@ define(["react", "splunkjs/splunk"], function(react, splunkSdk){
this.state = {
serverKey: '',
tenantId: '',
tenants: [],
errorMessage: '',
};

this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChangeServerKey = this.handleChangeServerKey.bind(this);
this.handleChangeTenantId = this.handleChangeTenantId.bind(this);
this.handleSubmitServerKey = this.handleSubmitServerKey.bind(this);
this.handleSubmitTenant = this.handleSubmitTenant.bind(this);
}

handleChange(event) {
handleChangeServerKey(event) {
this.setState({ ...this.state, [event.target.name]: event.target.value});
}

async handleSubmit(event) {
handleChangeTenantId(event) {
this.setState({ ...this.state, ["tenantId"]: event.target.value});
}

async handleSubmitServerKey(event) {
event.preventDefault();
Setup.retrieveUserTenants(splunkSdk, this.state.serverKey, (user_tenants) => {
if (user_tenants.tenants.length > 0) {
this.state.tenantId = user_tenants.tenants[0].id;
}
this.state.errorMessage = '';
this.setState({ ...this.state, ["tenants"]: user_tenants.tenants});
}, (error) => {
this.setState({ ...this.state, ["errorMessage"]: error});
});
}

async handleSubmitTenant(event) {
event.preventDefault();
await Setup.saveConfiguration(splunkSdk, this.state.serverKey, this.state.tenantId);
}

render() {
return e("div", null, [
e("h2", null, "Add a server key and tenant ID to complete app setup."),
e("div", null, [
e("form", { onSubmit: this.handleSubmit }, [
e("label", null, [
"Server API Key ",
e("input", { type: "password", name: "serverKey", value: this.state.serverKey, onChange: this.handleChange })
]),
e("label", null, [
"Tenant ID ",
e("input", { type: "number", name: "tenantId", value: this.state.tenantId, onChange: this.handleChange })
]),
e("input", { type: "submit", value: "Submit" })
const tenantOptions = [];
for (let i = 0; i < this.state.tenants.length; i++) {
tenantOptions.push(
e('option', { key: i, value: this.state.tenants[i].id }, this.state.tenants[i].name)
);
}
if (tenantOptions.length == 0 || this.state.serverKey == '') {
return e("div", null, [
e("h2", null, "Enter your API Key"),
e("p", null, "A new API Key can be generated by going on your profile page in Flare"),
e("p", { class: "error" }, this.state.errorMessage),
e("div", null, [
e("form", { onSubmit: this.handleSubmitServerKey }, [
e("label", null, [
"Server API Key ",
e("input", { type: "password", name: "serverKey", value: this.state.serverKey, onChange: this.handleChangeServerKey })
]),
e("input", { type: "submit", value: "Next Step" })
])
])
]);
} else {
return e("div", null, [
e("h2", null, "Please select the Tenant you want to ingest events from"),
e("p", { class: "error" }, this.state.errorMessage),
e("div", null, [
e("form", { onSubmit: this.handleSubmitTenant }, [
e(
'select',
{ name: 'tenantsDropdown', onChange: this.handleChangeTenantId },
...tenantOptions
),
e("br"),
e("input", { type: "submit", value: "Submit" })
])
])
])
]);
]);
}
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,25 @@ function createService(splunkSdk, applicationNamespace) {
return service;
};

async function retrieveUserTenants(splunkSdk, serverKey, successCallback, errorCallback) {
const service = createService(splunkSdk, applicationNameSpace);
const data = { "serverKey": serverKey};
service.post('/services/retrieve_user_tenants', data, function(err, response) {
if(err) {
errorCallback(err.data);
} else if(response.status === 200) {
successCallback(response.data);
}
});
}

async function saveConfiguration(splunkSdk, serverKey, tenantId) {
try {
const service = createService(splunkSdk, applicationNameSpace);

const storagePasswords = service.storagePasswords();
savePassword(storagePasswords, storageRealm, "serverkey", serverKey);
savePassword(storagePasswords, storageRealm, "tenantid", tenantId);
const storagePasswords = await promisify(service.storagePasswords().fetch)();
await savePassword(storagePasswords, storageRealm, "serverkey", serverKey);
await savePassword(storagePasswords, storageRealm, "tenantid", tenantId);

await completeSetup(service);
await reloadApp(service, appName);
Expand All @@ -56,7 +68,34 @@ async function saveConfiguration(splunkSdk, serverKey, tenantId) {
}
}

function savePassword(storage, storageRealm, key, value) {
function doesPasswordExist(
storage,
storageRealm,
key,
) {
var passwordList = storage.list();
const passwordId = storageRealm + ":" + key + ":";

for (var index = 0; index < passwordList.length; index++) {
if (passwordList[index].name === passwordId) {
return true
}
}
return false
};

async function savePassword(storage, storageRealm, key, value) {
var passwordExists = doesPasswordExist(storage, storageRealm, key);
if (passwordExists) {
const passwordId = storageRealm + ":" + key + ":";
storage.del(passwordId);
}

while (passwordExists) {
storage = await promisify(storage.fetch)();
passwordExists = doesPasswordExist(storage, storageRealm, key);
}

storage.create({
name: key,
realm: storageRealm,
Expand All @@ -72,4 +111,5 @@ function savePassword(storage, storageRealm, key, value) {

export {
saveConfiguration,
retrieveUserTenants,
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
function promisify(fn) {
console.log("promisify: Don't use this in production! Use a proper promisify library instead.")

// return a new promisified function
return (...args) => {
return new Promise((resolve, reject) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,8 @@
/* Styling for each line of the errors */
color: rgba(255, 0, 0, 1);
}

.error {
/* Styling for each line of the errors */
color: rgba(255, 0, 0, 1);
}
26 changes: 26 additions & 0 deletions flare_splunk_integration/bin/flare_external_requests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import sys
import os
import splunk # type: ignore[import-not-found]
import json
from typing import Any

sys.path.insert(0, os.path.join(os.path.dirname(__file__), "vendor"))
from flareio import FlareApiClient


def parseParams(payload: str) -> dict[str, Any]:
params = {}
for param_entry in payload.split("&"):
param = param_entry.split("=", 1)
params[param[0]] = param[1]
return params


class FlareUserTenants(splunk.rest.BaseRestHandler):
def handle_POST(self):
payload = self.request["payload"]
params = parseParams(payload)
self.flare_client = FlareApiClient(api_key=params["serverKey"])
user_tenants_response = self.flare_client.get("firework/v2/me/tenants")
self.response.setHeader("Content-Type", "application/json")
self.response.write(json.dumps(user_tenants_response.json()))
4 changes: 2 additions & 2 deletions flare_splunk_integration/default/app.conf
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ is_configured = 0
[ui]
is_visible = 1
label = Flare
setup_view = setupPageDashboard
setup_view = configuration
supported_themes = light, dark

[launcher]
author = Flare Systems
description =
version = 1.0.0
version = 1.0.1

[triggers]
reload.flare = simple
12 changes: 5 additions & 7 deletions flare_splunk_integration/default/data/ui/nav/default.xml
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<nav search_view="search">
<view name="search" default='true' />
<view name="analytics_workspace" />
<view name="datasets" />
<view name="reports" />
<view name="alerts" />
<view name="dashboards" />
<nav search_view="search" color="#272735">
<view name="home" default='true' />
<view name="search" />
<view name="configuration" />
<a href="https://app.flare.io/" target="_blank">Flare Platform</a>
</nav>
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<dashboard isDashboard="false" version="1.1"
<dashboard isDashboard="false" version="1.1" hideEdit="true"
script="javascript/setupPage.js"
stylesheet="styles/setupPage.css">
<label>Flare Integration Setup Page</label>
<label>Configuration</label>
<row>
<panel>
<html>
Expand Down
16 changes: 16 additions & 0 deletions flare_splunk_integration/default/data/ui/views/home.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<dashboard version="1.1" hideEdit="true">
<label>Home</label>

<row>
<panel>
<html>

<h1>What is Lorem Ipsum?</h1>
<p>
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
</p>

</html>
</panel>
</row>
</dashboard>
4 changes: 4 additions & 0 deletions flare_splunk_integration/default/restmap.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[script:flare_external_requests_user_tenants]
match=/retrieve_user_tenants
handler=flare_external_requests.FlareUserTenants
python.version = python3
3 changes: 3 additions & 0 deletions flare_splunk_integration/default/web.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[expose:flare_external_requests_user_tenants]
pattern=retrieve_user_tenants
methods=POST
1 change: 1 addition & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
[mypy]
exclude = (vendor*)/$
follow_imports = skip
mypy_path = flare_splunk_integration/bin/vendor

0 comments on commit 55f3616

Please sign in to comment.