Skip to content

Commit

Permalink
ui!
Browse files Browse the repository at this point in the history
  • Loading branch information
iloveicedgreentea committed Sep 12, 2023
1 parent 05b1e52 commit fc93e5c
Show file tree
Hide file tree
Showing 7 changed files with 823 additions and 54 deletions.
5 changes: 4 additions & 1 deletion api/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package api
import (
"fmt"
"os"

"path/filepath"

"github.com/gin-gonic/gin"
Expand Down Expand Up @@ -53,8 +54,10 @@ func GetConfig(c *gin.Context) {

func SaveConfig(c *gin.Context) {
var jsonData map[string]interface{}

if err := c.ShouldBindJSON(&jsonData); err != nil {
c.JSON(400, gin.H{"error": "Invalid Payload"})
c.JSON(400, gin.H{"error": err.Error()})
fmt.Println(c.Request.Body)
return
}

Expand Down
5 changes: 4 additions & 1 deletion changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,7 @@ Web UI flow for configuring server
New MQTT automations:
* "topicbeqcurrentprofile": -> which current profile is loaded
* "topicminidspmutestatus": -> true/false if minidsp(s) muted
* "topicplayingstatus": -> true/false if the tool is playing
* "topicplayingstatus": -> true/false if the tool is playing
Some functionality changes, check the readme
HDMI Sync automation in testing
*Breaking changes* All config fields are lowercase now
2 changes: 1 addition & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func main() {
// TODO: Engine.SetTrustedProxies(nil)
port := config.GetString("main.listenPort")
if port == "" {
log.Fatal("error getting port")
port = "9999"
}
log.Infof("Starting server on port %v", port)
if err := r.Run(fmt.Sprintf(":%s", port)); err != nil {
Expand Down
20 changes: 16 additions & 4 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ Meat & Potatos:
* Set Volume based on media type (movie, TV, etc)
* Trigger lights when playing, pausing, or stopping automatically
* HDMI Sync detection and automation (pause while HDMI is syncing so you don't sit embarrassed with a audio playing to a black screen)
* Web based UI for configuration

Other cool stuff:
* Mute/Unmute Minidsp automation for things like turning off subs at night
* Simple web based UI for configuration
* Detect aspect ratio and send command to HA to adjust accordingly
* Also supports using my MadVR Envy Home Assistant integration
* Various MQTT sensors for volume control, lights, mute status, and current BEQ profile
Expand All @@ -27,11 +27,23 @@ Note: this assumes you have ezBEQ, Plex, and HomeAssistant working. Refer to the

You don't strictly need HA and you can use your own systems but I recommend HA.

Simple Way:

1) Deploy via Docker -> `ghcr.io/iloveicedgreentea/plex-webhook-automation:$version`
2) Set up config via web ui -> `http://(server-ip):9999`
* can get UUID from `https://plex.tv/devices.xml`
3) Set up Plex to send webhooks to your server IP, `listenPort`, and the handler endpoint of `/plexwebhook`
* `(server-ip):9999/plexwebhook`
3) Whitelist your server IP in Plex so it can call the API without authentication.
4) Play a movie and check server logs. It should say what it loaded and you should see whatever options you enabled work.

Manual Way:

0) Create `config.json` and set the values appropriately. See below.
1) Either pull `ghcr.io/iloveicedgreentea/plex-webhook-automation:$version` or build the binary directly
* if you deploy a container, mount config.json to a volume called exactly `/config.json`
2) Set up Plex to send webhooks to your server IP, `listenPort`, and the handler endpoint of `/plexwebhook`
* `locahost:9999/plexwebhook`
* `(server-ip):9999/plexwebhook`
3) Whitelist your server IP in Plex so it can call the API without authentication. Plex refuses to implement local server auth with an API, so I don't want to implement their locked-in auth method that has historically had outages (8/30/23 is the latest one by the way).
4) Add your UUID to the config.json so it filters by device
* can get UUID from `https://plex.tv/devices.xml` or run the tool and play something, check the logs
Expand Down Expand Up @@ -330,11 +342,11 @@ create file named config.json, paste this in, remove the comments after
```json
{
// note the case is lowercase now
// note the case is lowercase
"homeassistant": {
"enabled": true,
"url": "http://123.123.123.123",
"port": "8123",
"enabled": true,
// get a token from your user profile
"token": "ey.xyzjwt",
// trigger functions to change the following
Expand Down
247 changes: 214 additions & 33 deletions web/app.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,220 @@
// Check if config exists first
fetch('/config-exists')
.then(response => response.json())
.then(data => {
if (data.exists) {
// Populate fields from existing config
fetch('/get-config')
.then(response => response.json())
.then(config => populateForm(config));
}
function populateFields(config) {
document.getElementById('listenport').value = config.main.listenport;
// .checked or .value for each kind for each item in HTML

// EZBeq
document.getElementById('ezbeq-adjustmastervolumewithprofile').checked = config.ezbeq.adjustmastervolumewithprofile;
document.getElementById('ezbeq-enabled').checked = config.ezbeq.enabled;
document.getElementById('ezbeq-denonip').value = config.ezbeq.denonip;
document.getElementById('ezbeq-denonport').value = config.ezbeq.denonport;
document.getElementById('ezbeq-dryrun').checked = config.ezbeq.dryrun;
document.getElementById('ezbeq-enabletvbeq').checked = config.ezbeq.enabletvbeq;
document.getElementById('ezbeq-notifyendpointname').value = config.ezbeq.notifyendpointname;
document.getElementById('ezbeq-notifyonload').checked = config.ezbeq.notifyonload;
document.getElementById('ezbeq-port').value = config.ezbeq.port;
document.getElementById('ezbeq-preferredauthor').value = config.ezbeq.preferredauthor;
const slotsArray = config.ezbeq.slots;
slotsArray.forEach(slot => {
document.getElementById(`slot${slot}`).checked = true;
});
document.getElementById('ezbeq-stopplexifmismatch').checked = config.ezbeq.stopplexifmismatch;
document.getElementById('ezbeq-url').value = config.ezbeq.url;
document.getElementById('ezbeq-useavrcodecsearch').checked = config.ezbeq.useavrcodecsearch;


// HomeAssistant
document.getElementById('homeassistant-enabled').checked = config.homeassistant.enabled;
document.getElementById('homeassistant-url').value = config.homeassistant.url;
document.getElementById('homeassistant-port').value = config.homeassistant.port;
document.getElementById('homeassistant-token').value = config.homeassistant.token;
document.getElementById('homeassistant-triggeraspectratiochangeonevent').checked = config.homeassistant.triggeraspectratiochangeonevent;
document.getElementById('homeassistant-triggerlightsonevent').checked = config.homeassistant.triggerlightsonevent;
document.getElementById('homeassistant-triggeravrmastervolumechangeonevent').checked = config.homeassistant.triggeravrmastervolumechangeonevent;
document.getElementById('homeassistant-remoteentityname').value = config.homeassistant.remoteentityname;
document.getElementById('homeassistant-playscriptname').value = config.homeassistant.playscriptname;
document.getElementById('homeassistant-pausescriptname').value = config.homeassistant.pausescriptname;
document.getElementById('homeassistant-stopscriptname').value = config.homeassistant.stopscriptname;

// MQTT
document.getElementById('mqtt-url').value = config.mqtt.url;
document.getElementById('mqtt-username').value = config.mqtt.username;
document.getElementById('mqtt-password').value = config.mqtt.password;
document.getElementById('mqtt-topiclights').value = config.mqtt.topiclights;
document.getElementById('mqtt-topicvolume').value = config.mqtt.topicvolume;
document.getElementById('mqtt-topicaspectratio').value = config.mqtt.topicaspectratio;
document.getElementById('mqtt-topicbeqcurrentprofile').value = config.mqtt.topicbeqcurrentprofile;
document.getElementById('mqtt-topicminidspmutestatus').value = config.mqtt.topicminidspmutestatus;
document.getElementById('mqtt-topicplayingstatus').value = config.mqtt.topicplayingstatus;

// Function to populate form
function populateForm(config) {
document.getElementById('url').value = config.homeassistant.url;
document.getElementById('port').value = config.homeassistant.port;
// Populate other fields
// Plex
document.getElementById('plex-enabled').checked = config.plex.enabled;
document.getElementById('plex-url').value = config.plex.url;
document.getElementById('plex-port').value = config.plex.port;
document.getElementById('plex-ownernamefilter').value = config.plex.ownernamefilter;
document.getElementById('plex-deviceuuidfilter').value = config.plex.deviceuuidfilter;
document.getElementById('plex-playermachineidentifier').value = config.plex.playermachineidentifier;
document.getElementById('plex-playerip').value = config.plex.playerip;
document.getElementById('plex-enabletrailersupport').checked = config.plex.enabletrailersupport;

// Signal
document.getElementById('signal-enabled').checked = config.signal.enabled;
document.getElementById('signal-source').value = config.signal.source;
}

// Function to submit form
function submitConfig() {
const formData = {
homeassistant: {
url: document.getElementById('url').value,
port: document.getElementById('port').value,
// ...other fields
},
// ...other sections
function buildFinalConfig() {
const slotsArray = [];
for (let i = 1; i <= 4; i++) {
if (document.getElementById(`slot${i}`).checked) {
slotsArray.push(i);
}
}
const ezbeqConfig = {
"adjustmastervolumewithprofile": document.getElementById('ezbeq-adjustmastervolumewithprofile').checked,
"enabled": document.getElementById('ezbeq-enabled').checked,
"denonip": document.getElementById('ezbeq-denonip').value,
"denonport": document.getElementById('ezbeq-denonport').value,
"dryrun": document.getElementById('ezbeq-dryrun').checked,
"enabletvbeq": document.getElementById('ezbeq-enabletvbeq').checked,
"notifyendpointname": document.getElementById('ezbeq-notifyendpointname').value,
"notifyonload": document.getElementById('ezbeq-notifyonload').checked,
"port": document.getElementById('ezbeq-port').value,
"preferredauthor": document.getElementById('ezbeq-preferredauthor').value,
"slots": slotsArray,
"stopplexifmismatch": document.getElementById('ezbeq-stopplexifmismatch').checked,
"url": document.getElementById('ezbeq-url').value,
"useavrcodecsearch": document.getElementById('ezbeq-useavrcodecsearch').checked
};
const homeAssistantConfig = {
"enabled": document.getElementById('homeassistant-enabled').checked,
"url": document.getElementById('homeassistant-url').value,
"port": document.getElementById('homeassistant-port').value,
"token": document.getElementById('homeassistant-token').value,
"triggeraspectratiochangeonevent": document.getElementById('homeassistant-triggeraspectratiochangeonevent').checked,
"triggerlightsonevent": document.getElementById('homeassistant-triggerlightsonevent').checked,
"triggeravrmastervolumechangeonevent": document.getElementById('homeassistant-triggeravrmastervolumechangeonevent').checked,
"remoteentityname": document.getElementById('homeassistant-remoteentityname').value,
"playscriptname": document.getElementById('homeassistant-playscriptname').value,
"pausescriptname": document.getElementById('homeassistant-pausescriptname').value,
"stopscriptname": document.getElementById('homeassistant-stopscriptname').value
};

const mainConfig = {
"listenport": document.getElementById('listenport').value
};
fetch('/save-config', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
})
.then(response => response.json())
.then(data => alert(data.message));

const mqttConfig = {
"url": document.getElementById('mqtt-url').value,
"username": document.getElementById('mqtt-username').value,
"password": document.getElementById('mqtt-password').value,
"topiclights": document.getElementById('mqtt-topiclights').value,
"topicvolume": document.getElementById('mqtt-topicvolume').value,
"topicaspectratio": document.getElementById('mqtt-topicaspectratio').value,
"topicbeqcurrentprofile": document.getElementById('mqtt-topicbeqcurrentprofile').value,
"topicminidspmutestatus": document.getElementById('mqtt-topicminidspmutestatus').value,
"topicplayingstatus": document.getElementById('mqtt-topicplayingstatus').value
};

const plexConfig = {
"enabled": document.getElementById('plex-enabled').checked,
"url": document.getElementById('plex-url').value,
"port": document.getElementById('plex-port').value,
"ownernamefilter": document.getElementById('plex-ownernamefilter').value,
"deviceuuidfilter": document.getElementById('plex-deviceuuidfilter').value,
"playermachineidentifier": document.getElementById('plex-playermachineidentifier').value,
"playerip": document.getElementById('plex-playerip').value,
"enabletrailersupport": document.getElementById('plex-enabletrailersupport').checked
};
const signalConfig = {
"enabled": document.getElementById('signal-enabled').checked,
"source": document.getElementById('signal-source').value
};
// Build the final config JSON
const finalConfig = {
"ezbeq": ezbeqConfig,
"homeassistant": homeAssistantConfig,
"main": mainConfig,
"mqtt": mqttConfig,
"plex": plexConfig,
"signal": signalConfig
};

return finalConfig;

}


document.addEventListener('DOMContentLoaded', function () {
async function loadConfig() {
const response = await fetch('/get-config');
if (response.ok) {
return await response.json();
} else {
throw new Error('Failed to fetch config');
}
}

loadConfig()
.then(config => {
// Populate fields from config
populateFields(config);
})
.catch(error => {
console.error('Error in loading config:', error);
});


document.getElementById('ezbeqForm').addEventListener('submit', async function (e) {
e.preventDefault();

// We moved the logic to build the finalConfig object here
const finalConfig = buildFinalConfig();
console.log(JSON.stringify(finalConfig))
try {
await submitConfig(finalConfig);
showNotification("Configuration saved successfully.");
} catch (error) {
showNotification("Failed to save configuration.", false);
}
});

});
async function submitConfig(data) {
try {
const response = await fetch('/save-config', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});

if (response.ok) {
return await response.json();
} else {
const text = await response.text(); // Get more information from the server
throw new Error(`Failed to save configuration, server says: ${text}`);
}
} catch (error) {
console.error("An error occurred while trying to send the request:", error);
throw error; // Re-throw to be caught by the caller
}
}



function showNotification(message, isSuccess = true) {
const notification = document.getElementById("notification");
notification.textContent = message;

if (isSuccess) {
notification.className = "notification-success";
} else {
notification.className = "notification-error";
}

// Remove the notification after 4 seconds
setTimeout(() => {
notification.textContent = "";
notification.className = "";
}, 4000);
}
Loading

0 comments on commit fc93e5c

Please sign in to comment.