Skip to content

Commit

Permalink
feat: system tray custom menu & podman update modal (#597)
Browse files Browse the repository at this point in the history
* added podman modal

* fixed developer menu, added mac os version check for podman 5

* fixed preferences modal width

* fixed banner configs, added updateAvailable

* added openPodmanModal

* added window focus/creation when selecting podman status

* added node package open through tray

* added alert dialog

* custom menu working

* added theme support, and first try at ipc commands

* separate out native and custom menu, add node status checking func

* improved node package status for running

* added svg icons

* added styling for stopped nodes

* stronger logic for status and icons

* style changes. prepare to try react component

* refined podman status

* improved logic on banners

* disable click prevention in case user quits modal

* added additional errors for nodes

* added additional positioning for other OS

* fix: put tray files in assets as main loads them

* replaced icons, added conditional on separator

* stop all nodes and podman machine on tray quit

* enabled custom tray only for mac

* use modelRoutes.podman in sidebar

---------

Authored-by: corn-potage
  • Loading branch information
corn-potage authored Jul 30, 2024
1 parent 4c1e078 commit 393704e
Show file tree
Hide file tree
Showing 30 changed files with 846 additions and 240 deletions.
3 changes: 3 additions & 0 deletions assets/icons/tray/status/error.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions assets/icons/tray/status/stopped.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions assets/icons/tray/status/synced.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions assets/icons/tray/status/syncing.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions assets/locales/en/systemRequirements.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
{
"macOSTitle": "MacOS version is at least {{minVersion}} to run Podman 5.0",
"macOSDescription": "MacOS version: {{version}}",
"processorCoresTitle": "Processor has {{minCores}} cores or more",
"processorCoresDescription": "Processor cores: {{cores}}",
"memorySizeTitle": "At least {{minSize}}GB of system memory (RAM)",
Expand Down
7 changes: 6 additions & 1 deletion assets/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -168,5 +168,10 @@
"CalculatingDataSize": "(calculating data size...)",
"KeepNodeData": "Keep node related chain data {{data}}",
"NoResults": "No results",
"TrySearching": "Try searching for another keyword or clear all filters"
"TrySearching": "Try searching for another keyword or clear all filters",
"RunningOutdatedPodman": "You are running an outdated version of Podman",
"CurrentPodman": "Your current Podman installation ({{currentPodmanVersion}}) is incompatible with NiceNode and requires version {{requiredPodmanVersion}} or higher for it to run.",
"PodmanIsRequiredComponent": "Podman is a required component for NiceNode to run the many client options. Podman facilitates the running of containers within a virtualised Linux environment and will operate in the background.",
"DownloadAndUpdate": "Download and update",
"PodmanUpdate": "Podman update"
}
103 changes: 103 additions & 0 deletions assets/trayIndex.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Tray Menu</title>
<style>
body {
margin: 0;
padding: 6px;
font-size: 12.5px;
font-family: Arial, sans-serif;
/* background: #2b2b2b; */
color: black;
}

body.dark {
color: white; /* Color for dark theme */
}

body.light {
color: black; /* Color for light theme */
}

.menu-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 4px 10px;
}

.light .menu-item.stopped {
color: rgba(0, 0, 0, 0.25);
}

.dark .menu-item.stopped {
color: rgba(255,255,255,0.25);
}

.menu-item:hover {
border-radius: 4px;
color: white;
background: rgba(0, 122, 255, 0.8);
}

.menu-item.stopped:hover {
background: none;
}

.dark .separator {
border-bottom: 1px solid rgba(255, 255, 255, 0.1)
}

.light .separator {
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}

.separator {
margin: 3px 0;
}

.menu-status-container {
display: flex;
align-items: center;
}

.light .menu-status-container {
color: rgba(0, 0, 0, 0.5);
}

.light .menu-item.stopped .menu-status-container {
color: rgba(0, 0, 0, 0.25);
}

.dark .menu-status-container {
color: rgba(255,255,255,0.5);
}

.dark .menu-item.stopped .menu-status-container {
color: rgba(255,255,255,0.25);
}

.menu-status {
text-transform: capitalize;
}

.menu-status-icon {
display: flex;
align-items: center;
margin-right: 5px;
}

.status-icon {
width: 12px; /* Adjust size as needed */
height: 12px; /* Adjust size as needed */
vertical-align: middle; /* Align icon with text */
}
</style>
</head>
<body>
<div id="menu-container"></div>
<script src="trayIndex.js"></script>
</body>
</html>
131 changes: 131 additions & 0 deletions assets/trayIndex.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
const { ipcRenderer } = require('electron');

const getIconKey = (status) => {
switch (status) {
case 'running':
case 'starting':
return 'syncing';
//TODO: consider camelcased strings
case 'error running':
case 'error starting':
case 'error stopping':
case 'notInstalled':
case 'notRunning':
case 'isOutdated':
return 'error';
default:
return status;
}
};

const getStatusText = (status) => {
switch (status) {
case 'notInstalled':
return 'Not Installed';
case 'notRunning':
return 'Not Running';
case 'isOutdated':
return 'Update Now';
default:
return status;
}
};

ipcRenderer.on(
'update-menu',
(event, { nodePackageTrayMenu, podmanMenuItem, statusIcons }) => {
const menuItems = [
...nodePackageTrayMenu.map((item) => ({
name: item.name,
status: item.status,
action: () => ipcRenderer.send('node-package-click', item.id),
})),
...(nodePackageTrayMenu.length >= 1 ? [{ separator: true }] : []),
...(podmanMenuItem.status !== 'isRunning'
? [
{
name: 'Podman',
status: podmanMenuItem.status,
action: () => ipcRenderer.send('podman-click'),
},
{ separator: true },
]
: []),
{
name: 'Open NiceNode',
action: () => ipcRenderer.send('show-main-window'),
},
{ name: 'Quit', action: () => ipcRenderer.send('quit-app') },
];

const container = document.getElementById('menu-container');
container.innerHTML = ''; // Clear existing items

menuItems.forEach((item) => {
if (item.separator) {
const separator = document.createElement('div');
separator.className = 'separator';
container.appendChild(separator);
} else {
const menuItem = document.createElement('div');
menuItem.className = 'menu-item';

if (item.status === 'stopped') {
menuItem.classList.add('stopped');
}

const nameSpan = document.createElement('span');
nameSpan.textContent = item.name;
menuItem.appendChild(nameSpan);

if (item.status) {
const statusContainer = document.createElement('div');
statusContainer.className = 'menu-status-container';

const statusIconContainer = document.createElement('div');
statusIconContainer.className = 'menu-status-icon';

const statusIcon = document.createElement('div');
statusIcon.innerHTML =
statusIcons[getIconKey(item.status)] || statusIcons.default;
statusIcon.className = 'status-icon';

const statusText = document.createElement('div');
statusText.className = 'menu-status';
statusText.textContent = getStatusText(item.status);

statusIconContainer.appendChild(statusIcon);
statusContainer.appendChild(statusIconContainer);
statusContainer.appendChild(statusText);
menuItem.appendChild(statusContainer);
}

menuItem.addEventListener('click', item.action);

container.appendChild(menuItem);
}
});
ipcRenderer.send('adjust-height', document.body.scrollHeight);
},
);

ipcRenderer.on('set-theme', (event, theme) => {
applyTheme(theme);
});

// Apply theme-based styles
const applyTheme = (theme) => {
const body = document.body;
const menuItems = document.querySelectorAll('.menu-item');
if (theme === 'dark') {
body.classList.add('dark');
body.classList.remove('light');
} else {
body.classList.add('light');
body.classList.remove('dark');
}
};

ipcRenderer.on('update-menu', (event, updatedItems) => {
// Update menu items if necessary
});
2 changes: 2 additions & 0 deletions src/main/messenger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export const CHANNELS = {
nodeLogs: 'nodeLogs',
podman: 'podman',
podmanInstall: 'podmanInstall',
openPodmanModal: 'openPodmanModal',
openNodePackageScreen: 'openNodePackageScreen',
theme: 'theme',
notifications: 'notifications',
reportEvent: 'reportEvent',
Expand Down
6 changes: 5 additions & 1 deletion src/main/nn-auto-updater/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,11 @@ export class nnAutoUpdater
if (isLinux()) {
console.log('nnAutoUpdater setFeedURL in linux!');
} else {
this.nativeUpdater.setFeedURL(options);
try {
this.nativeUpdater.setFeedURL(options);
} catch (e) {
console.error('Error in setFeedURL: ', e);
}
}
}
}
Expand Down
5 changes: 4 additions & 1 deletion src/main/nodePackageManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ export const startNodePackage = async (nodeId: NodeId) => {
node.lastStartedTimestampMs = Date.now();
node.stoppedBy = undefined;
nodePackageStore.updateNodePackage(node);
let allServicesStarted = true;

const isEthereumPackage = node.spec.specId === 'ethereum';

Expand All @@ -153,6 +154,7 @@ export const startNodePackage = async (nodeId: NodeId) => {
} catch (e) {
logger.error(`Unable to start node service: ${JSON.stringify(service)}`);
nodePackageStatus = NodeStatus.errorStarting;
allServicesStarted = false;
// try to start all services, or stop other services?
// todo: set as partially started?
// throw e;
Expand Down Expand Up @@ -182,7 +184,8 @@ export const startNodePackage = async (nodeId: NodeId) => {
}

// If all node services start without error, the package is considered running
if (nodePackageStatus === NodeStatus.running) {
if (allServicesStarted) {
nodePackageStatus = NodeStatus.running;
setLastRunningTime(nodeId, 'node');
}

Expand Down
6 changes: 5 additions & 1 deletion src/main/podman/podman.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
type ConfigValuesMap,
buildCliConfig,
} from '../../common/nodeConfig';
import { send } from '../messenger';
import { CHANNELS, send } from '../messenger.js';
import { restartNodes } from '../nodePackageManager';
import { isLinux } from '../platform';
import { killChildProcess } from '../processExit';
Expand Down Expand Up @@ -779,6 +779,10 @@ export const isPodmanStarting = async () => {
return bIsPodmanStarting;
};

export const openPodmanModal = async () => {
send(CHANNELS.openPodmanModal);
};

// todoo
// setTimeout(() => {
// isPodmanRunning();
Expand Down
4 changes: 4 additions & 0 deletions src/main/state/nodePackages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,7 @@ export const getUserNodePackagesWithNodes = async () => {
});
return userNodePackages;
};

export const openNodePackageScreen = async (nodeId: NodeId) => {
send(CHANNELS.openNodePackageScreen, nodeId);
};
Loading

0 comments on commit 393704e

Please sign in to comment.