diff --git a/assets/icons/tray/status/error.svg b/assets/icons/tray/status/error.svg
new file mode 100644
index 000000000..998c06e2f
--- /dev/null
+++ b/assets/icons/tray/status/error.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/tray/status/stopped.svg b/assets/icons/tray/status/stopped.svg
new file mode 100644
index 000000000..62e03c0d3
--- /dev/null
+++ b/assets/icons/tray/status/stopped.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/tray/status/synced.svg b/assets/icons/tray/status/synced.svg
new file mode 100644
index 000000000..1e99f1228
--- /dev/null
+++ b/assets/icons/tray/status/synced.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/tray/status/syncing.svg b/assets/icons/tray/status/syncing.svg
new file mode 100644
index 000000000..c1185c77f
--- /dev/null
+++ b/assets/icons/tray/status/syncing.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/locales/en/systemRequirements.json b/assets/locales/en/systemRequirements.json
index 919216bdc..8be9abf6f 100644
--- a/assets/locales/en/systemRequirements.json
+++ b/assets/locales/en/systemRequirements.json
@@ -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)",
diff --git a/assets/locales/en/translation.json b/assets/locales/en/translation.json
index 9242f684b..0fac93e54 100644
--- a/assets/locales/en/translation.json
+++ b/assets/locales/en/translation.json
@@ -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"
}
diff --git a/assets/trayIndex.html b/assets/trayIndex.html
new file mode 100644
index 000000000..2b6f28205
--- /dev/null
+++ b/assets/trayIndex.html
@@ -0,0 +1,103 @@
+
+
+
+
+ Tray Menu
+
+
+
+
+
+
+
diff --git a/assets/trayIndex.js b/assets/trayIndex.js
new file mode 100644
index 000000000..ffa90bf82
--- /dev/null
+++ b/assets/trayIndex.js
@@ -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
+});
diff --git a/src/main/messenger.ts b/src/main/messenger.ts
index 95dbb7d8f..1064808a7 100644
--- a/src/main/messenger.ts
+++ b/src/main/messenger.ts
@@ -22,6 +22,8 @@ export const CHANNELS = {
nodeLogs: 'nodeLogs',
podman: 'podman',
podmanInstall: 'podmanInstall',
+ openPodmanModal: 'openPodmanModal',
+ openNodePackageScreen: 'openNodePackageScreen',
theme: 'theme',
notifications: 'notifications',
reportEvent: 'reportEvent',
diff --git a/src/main/nn-auto-updater/main.ts b/src/main/nn-auto-updater/main.ts
index 991190dd3..5bbb83f33 100644
--- a/src/main/nn-auto-updater/main.ts
+++ b/src/main/nn-auto-updater/main.ts
@@ -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);
+ }
}
}
}
diff --git a/src/main/nodePackageManager.ts b/src/main/nodePackageManager.ts
index f80e8868a..5520054aa 100644
--- a/src/main/nodePackageManager.ts
+++ b/src/main/nodePackageManager.ts
@@ -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';
@@ -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;
@@ -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');
}
diff --git a/src/main/podman/podman.ts b/src/main/podman/podman.ts
index 9b4d4d3a8..eb4ed13cf 100644
--- a/src/main/podman/podman.ts
+++ b/src/main/podman/podman.ts
@@ -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';
@@ -779,6 +779,10 @@ export const isPodmanStarting = async () => {
return bIsPodmanStarting;
};
+export const openPodmanModal = async () => {
+ send(CHANNELS.openPodmanModal);
+};
+
// todoo
// setTimeout(() => {
// isPodmanRunning();
diff --git a/src/main/state/nodePackages.ts b/src/main/state/nodePackages.ts
index d63f59c4c..f64d5c36d 100644
--- a/src/main/state/nodePackages.ts
+++ b/src/main/state/nodePackages.ts
@@ -206,3 +206,7 @@ export const getUserNodePackagesWithNodes = async () => {
});
return userNodePackages;
};
+
+export const openNodePackageScreen = async (nodeId: NodeId) => {
+ send(CHANNELS.openNodePackageScreen, nodeId);
+};
diff --git a/src/main/tray.ts b/src/main/tray.ts
index 1c0e1e905..ecfc5f3ae 100644
--- a/src/main/tray.ts
+++ b/src/main/tray.ts
@@ -1,20 +1,32 @@
-import { Menu, MenuItem, Tray } from 'electron';
+import fs from 'node:fs/promises';
+import {
+ BrowserWindow,
+ Menu,
+ MenuItem,
+ Tray,
+ dialog,
+ ipcMain,
+ nativeTheme,
+ screen,
+} from 'electron';
+import { NodeStoppedBy } from '../common/node.js';
import logger from './logger';
import { createWindow, fullQuit, getMainWindow } from './main';
-import { isLinux, isWindows } from './platform';
-import {
- getNiceNodeMachine,
- startMachineIfCreated,
- stopMachineIfCreated,
-} from './podman/machine';
+import { stopAllNodePackages } from './nodePackageManager.js';
+import { isLinux, isMac, isWindows } from './platform';
+import { getPodmanDetails } from './podman/details.js';
+import { getNiceNodeMachine, stopMachineIfCreated } from './podman/machine';
+import { openPodmanModal } from './podman/podman.js';
import { getUserNodePackages } from './state/nodePackages';
+import { openNodePackageScreen } from './state/nodePackages.js';
// Can't import from main because of circular dependency
let _getAssetPath: (...paths: string[]) => string;
let tray: Tray;
+let trayWindow: BrowserWindow | null = null;
-// Can get asyncronously updated separately
+// Can get asynchronously updated separately
let nodePackageTrayMenu: { label: string; click: () => void }[] = [];
let podmanTrayMenu: MenuItem[] = [];
let openNiceNodeMenu: { label: string; click: () => void }[] = [];
@@ -33,25 +45,58 @@ export const setTrayIcon = (style: 'Default' | 'Alert') => {
}
};
+const openOrFocusWindow = () => {
+ const mainWindow = getMainWindow();
+ if (mainWindow === null) {
+ // Create a new window if none exists
+ createWindow();
+ } else {
+ // Show and focus on the existing window
+ mainWindow.show();
+ if (mainWindow.isMinimized()) {
+ mainWindow.restore(); // Restore if minimized
+ }
+ mainWindow.focus(); // Focus on the window
+ }
+};
+
export const setTrayMenu = () => {
const menuTemplate = [
- // todo: change icon if there are any status errors
+ // Podman status with start, stop, and delete?
...nodePackageTrayMenu,
{ type: 'separator' },
- // todo: show in developer mode
-
- // todo: add podman status with start, stop, and delete?
+ ...podmanTrayMenu,
+ { type: 'separator' },
...openNiceNodeMenu,
{
- label: 'Full Quit',
+ label: 'Quit',
click: () => {
- fullQuit(); // app no longer runs in the background
+ // Show confirmation dialog before quitting
+ // TODO: get translated strings for this
+ dialog
+ .showMessageBox({
+ type: 'question',
+ buttons: ['Yes', 'No'],
+ defaultId: 1, // Focus on 'No' by default
+ title: 'Confirm',
+ message: 'Are you sure you want to quit? Nodes will stop syncing.',
+ detail: 'Confirming will close the application.',
+ })
+ .then(async (result) => {
+ if (result.response === 0) {
+ // The 'Yes' button is at index 0
+ await stopAllNodePackages(NodeStoppedBy.shutdown);
+ await stopMachineIfCreated();
+ fullQuit(); // app no longer runs in the background
+ }
+ // Do nothing if the user selects 'No'
+ })
+ .catch((err) => {
+ logger.error('Error showing dialog:', err);
+ });
},
},
];
- if (process.env.NODE_ENV === 'development') {
- menuTemplate.push(...podmanTrayMenu, { type: 'separator' });
- }
const contextMenu = Menu.buildFromTemplate(menuTemplate as MenuItem[]);
if (tray) {
@@ -60,18 +105,14 @@ export const setTrayMenu = () => {
};
const getOpenNiceNodeMenu = () => {
- if (getMainWindow() === null) {
- openNiceNodeMenu = [
- {
- label: 'Open NiceNode',
- click: () => {
- createWindow(); // app no longer runs in the background
- },
+ openNiceNodeMenu = [
+ {
+ label: 'Open NiceNode',
+ click: () => {
+ openOrFocusWindow();
},
- ];
- } else {
- openNiceNodeMenu = [];
- }
+ },
+ ];
setTrayMenu();
};
@@ -84,8 +125,10 @@ const getNodePackageListMenu = () => {
isAlert = true;
}
return {
- label: `${nodePackage.spec.displayName} Node ${nodePackage.status}`,
+ label: `${nodePackage.spec.displayName} Node ${nodePackage.status}`,
click: () => {
+ openOrFocusWindow();
+ openNodePackageScreen(nodePackage.id);
logger.info(`clicked on ${nodePackage.spec.displayName}`);
},
};
@@ -126,8 +169,9 @@ const getPodmanMenuItem = async () => {
// stop
stopMachineIfCreated();
} else {
- // try to start if any other start
- startMachineIfCreated();
+ openOrFocusWindow();
+ openPodmanModal();
+ // startMachineIfCreated();
}
},
type: 'checkbox',
@@ -143,23 +187,267 @@ export const updateTrayMenu = () => {
getOpenNiceNodeMenu();
};
+function createCustomTrayWindow() {
+ trayWindow = new BrowserWindow({
+ // width: 277,
+ height: 100, // Initial height
+ show: false,
+ frame: false,
+ resizable: false,
+ transparent: true,
+ webPreferences: {
+ contextIsolation: false,
+ nodeIntegration: true,
+ },
+ vibrancy: isMac() ? 'sidebar' : undefined,
+ });
+
+ trayWindow.loadURL(`file://${_getAssetPath('trayIndex.html')}`);
+
+ trayWindow.on('blur', () => {
+ if (trayWindow) {
+ trayWindow.hide();
+ }
+ });
+
+ nativeTheme.on('updated', () => {
+ const theme = nativeTheme.shouldUseDarkColors ? 'dark' : 'light';
+ trayWindow?.webContents.send('set-theme', theme);
+ });
+
+ trayWindow.webContents.on('did-finish-load', () => {
+ updateCustomTrayMenu();
+ });
+
+ ipcMain.on('adjust-height', (event, height) => {
+ if (trayWindow) {
+ trayWindow.setSize(300, height);
+ }
+ });
+
+ ipcMain.on('node-package-click', (event, id) => {
+ openOrFocusWindow();
+ openNodePackageScreen(id);
+ logger.info(`clicked on node package with id ${id}`);
+ });
+
+ ipcMain.on('podman-click', async (event) => {
+ const { status } = await getCustomPodmanMenuItem();
+ logger.info('clicked on podman machine');
+ if (status === 'Running' || status === 'Starting') {
+ stopMachineIfCreated();
+ } else {
+ openOrFocusWindow();
+ openPodmanModal();
+ }
+ });
+
+ ipcMain.on('show-main-window', async (event) => {
+ logger.info('clicked on show-main-window');
+ openOrFocusWindow();
+ });
+
+ ipcMain.on('quit-app', (event) => {
+ dialog
+ .showMessageBox({
+ type: 'question',
+ buttons: ['Yes', 'No'],
+ defaultId: 1, // Focus on 'No' by default
+ title: 'Confirm',
+ message: 'Are you sure you want to quit? Nodes will stop syncing.',
+ detail: 'Confirming will close the application.',
+ })
+ .then(async (result) => {
+ if (result.response === 0) {
+ // The 'Yes' button is at index 0
+ await stopAllNodePackages(NodeStoppedBy.shutdown);
+ await stopMachineIfCreated();
+ fullQuit(); // app no longer runs in the background
+ }
+ // Do nothing if the user selects 'No'
+ })
+ .catch((err) => {
+ logger.error('Error showing dialog:', err);
+ });
+ });
+}
+
+const getCustomNodePackageListMenu = () => {
+ const userNodes = getUserNodePackages();
+ let isAlert = false;
+ const nodePackageTrayMenu = userNodes.nodeIds.map((nodeId) => {
+ const nodePackage = userNodes.nodes[nodeId];
+ if (nodePackage.status.toLowerCase().includes('error')) {
+ isAlert = true;
+ }
+ return {
+ //TODO: localization
+ name: `${nodePackage.spec.displayName} Node`,
+ status: nodePackage.status,
+ id: nodePackage.id,
+ };
+ });
+
+ console.log('getCustomNodePackageListMenu');
+ return { nodePackageTrayMenu, isAlert };
+};
+
+const getCustomPodmanMenuItem = async () => {
+ if (isLinux()) {
+ return { status: 'N/A' };
+ }
+ let status = 'notInstalled';
+ try {
+ const podmanMachine = await getNiceNodeMachine();
+ if (podmanMachine) {
+ const podmanDetails = await getPodmanDetails();
+ switch (true) {
+ case !podmanDetails.isInstalled:
+ status = 'notInstalled';
+ break;
+ case podmanDetails.isOutdated:
+ status = 'isOutdated';
+ break;
+ case !podmanDetails.isRunning:
+ status = 'notRunning';
+ break;
+ case podmanDetails.isRunning:
+ status = 'isRunning';
+ break;
+ default:
+ status = 'isRunning';
+ }
+ }
+ } catch (e) {
+ console.error('tray podmanMachine error: ', e);
+ status = 'Not found';
+ }
+ return { status };
+};
+
+const svgCache = new Map();
+
+const readSVGContent = async (filePath: string) => {
+ if (svgCache.has(filePath)) {
+ return svgCache.get(filePath);
+ }
+
+ try {
+ const data = await fs.readFile(filePath, 'utf8');
+ svgCache.set(filePath, data);
+ return data;
+ } catch (error) {
+ console.error(`Error reading file from path ${filePath}`, error);
+ return '';
+ }
+};
+
+export const updateCustomTrayMenu = async () => {
+ const { nodePackageTrayMenu, isAlert } = getCustomNodePackageListMenu();
+ const podmanMenuItem = await getCustomPodmanMenuItem();
+
+ const statusIcons = {
+ synced: await readSVGContent(
+ _getAssetPath('icons', 'tray', 'status', 'synced.svg'),
+ ),
+ error: await readSVGContent(
+ _getAssetPath('icons', 'tray', 'status', 'error.svg'),
+ ),
+ syncing: await readSVGContent(
+ _getAssetPath('icons', 'tray', 'status', 'syncing.svg'),
+ ),
+ default: await readSVGContent(
+ _getAssetPath('icons', 'tray', 'status', 'syncing.svg'),
+ ),
+ stopped: await readSVGContent(
+ _getAssetPath('icons', 'tray', 'status', 'stopped.svg'),
+ ),
+ };
+
+ if (trayWindow) {
+ trayWindow.webContents.send('update-menu', {
+ nodePackageTrayMenu,
+ podmanMenuItem,
+ statusIcons,
+ });
+ }
+ setTrayIcon(isAlert ? 'Alert' : 'Default');
+};
+
+function toggleCustomTrayWindow() {
+ if (trayWindow.isVisible()) {
+ trayWindow.hide();
+ } else {
+ const trayBounds = tray.getBounds(); // Get the bounds of the tray icon
+ const windowBounds = trayWindow.getBounds();
+ let x;
+ let y;
+
+ if (isMac()) {
+ x = Math.round(
+ trayBounds.x + trayBounds.width / 2 - windowBounds.width / 2,
+ );
+ y = Math.round(trayBounds.y + trayBounds.height);
+ } else if (isWindows()) {
+ const display = screen.getPrimaryDisplay(); // Get the primary display details
+ const workArea = display.workArea;
+ x = Math.round(
+ trayBounds.x + trayBounds.width / 2 - windowBounds.width / 2,
+ );
+
+ // Check if taskbar is at the bottom or the top
+ if (workArea.y < trayBounds.y) {
+ y = Math.round(trayBounds.y - windowBounds.height); // Taskbar is at the bottom
+ } else {
+ y = Math.round(trayBounds.y + trayBounds.height); // Taskbar is at the top
+ }
+ } else {
+ // Assume Linux behaves like Windows in this context
+ // This could require adjustments based on the Linux distro and environment
+ const display = screen.getPrimaryDisplay();
+ const workArea = display.workArea;
+ x = Math.round(
+ trayBounds.x + trayBounds.width / 2 - windowBounds.width / 2,
+ );
+ y =
+ workArea.y < trayBounds.y
+ ? Math.round(trayBounds.y - windowBounds.height)
+ : Math.round(trayBounds.y + trayBounds.height);
+ }
+
+ trayWindow.setPosition(x, y, false);
+ trayWindow.show();
+ trayWindow.focus();
+ }
+}
+
export const initialize = (getAssetPath: (...paths: string[]) => string) => {
logger.info('tray initializing...');
_getAssetPath = getAssetPath;
+
let icon = getAssetPath('icons', 'tray', 'NNIconDefaultInvertedTemplate.png');
if (isWindows()) {
icon = getAssetPath('icon.ico');
}
tray = new Tray(icon);
- // on windows, show a colored icon, 64x64 default icon seems ok
- updateTrayMenu();
- // Update the status of everything in the tray when it is opened
+ if (isMac()) {
+ createCustomTrayWindow();
+ } else {
+ updateTrayMenu();
+ }
+
tray.on('click', () => {
// on windows, default is open/show window on click
// on mac, default is open menu on click (no code needed)
// on linux?
- updateTrayMenu();
+ if (isMac()) {
+ toggleCustomTrayWindow();
+ updateCustomTrayMenu();
+ } else {
+ updateTrayMenu();
+ }
+
if (isWindows()) {
const window = getMainWindow();
if (window) {
@@ -171,8 +459,10 @@ export const initialize = (getAssetPath: (...paths: string[]) => string) => {
}
}
});
+
// on windows, the menu opens with a right click (no code needed)
// also, the 'right-click' event is not triggered on windows
+
logger.info('tray initialized');
};
diff --git a/src/renderer/Generics/redesign/Banner/Banner.tsx b/src/renderer/Generics/redesign/Banner/Banner.tsx
index 9d3e4e9fe..f5e271d45 100644
--- a/src/renderer/Generics/redesign/Banner/Banner.tsx
+++ b/src/renderer/Generics/redesign/Banner/Banner.tsx
@@ -45,7 +45,7 @@ export const Banner = ({
const [iconId, setIconId] = useState('blank');
const [title, setTitle] = useState('');
const [loading, setLoading] = useState(false);
- const [isClicked, setIsClicked] = useState(false);
+ // const [isClicked, setIsClicked] = useState(false);
const { t: g } = useTranslation('genericComponents');
useEffect(() => {
@@ -53,7 +53,7 @@ export const Banner = ({
setIconId('boltstrike');
setTitle(g('CurrentlyOffline'));
setDescription(g('PleaseReconnect'));
- } else if (!podmanInstalled) {
+ } else if (podmanInstalled === false) {
setIconId('warningcircle');
setTitle(g('PodmanIsNotInstalled'));
setDescription(g('ClickToInstallPodman'));
@@ -66,14 +66,14 @@ export const Banner = ({
setTitle(g('PodmanIsNotRunning'));
setDescription(g('ClickToStartPodman'));
}
- }, [offline, updateAvailable, podmanStopped, podmanInstalled, g]);
+ }, [offline, updateAvailable, podmanStopped, podmanInstalled]);
const onClickBanner = () => {
- if (isClicked || offline) {
+ if (offline) {
return;
}
- setIsClicked(true);
+ // setIsClicked(true);
if (!podmanInstalled) {
setDescription(g('PodmanInstalling'));
diff --git a/src/renderer/Generics/redesign/Modal/modal.css.ts b/src/renderer/Generics/redesign/Modal/modal.css.ts
index 07aa71157..6e58deb36 100644
--- a/src/renderer/Generics/redesign/Modal/modal.css.ts
+++ b/src/renderer/Generics/redesign/Modal/modal.css.ts
@@ -44,6 +44,12 @@ export const modalContentStyle = style({
'&.failSystemRequirements': {
width: '380px',
},
+ '&.podman': {
+ width: '624px',
+ },
+ '&.preferences': {
+ width: '624px',
+ },
},
});
diff --git a/src/renderer/Presentational/ModalManager/ModalManager.tsx b/src/renderer/Presentational/ModalManager/ModalManager.tsx
index 5ecb5a6c4..d4ba3094e 100644
--- a/src/renderer/Presentational/ModalManager/ModalManager.tsx
+++ b/src/renderer/Presentational/ModalManager/ModalManager.tsx
@@ -8,6 +8,7 @@ import { AlphaBuildModal } from './AlphaBuildModal';
import { ControllerUpdateModal } from './ControllerUpdateModal.js';
import FailSystemRequirementsModal from './FailSystemRequirementsModal';
import { NodeSettingsModal } from './NodeSettingsModal';
+import { PodmanModal } from './PodmanModal';
import { PreferencesModal } from './PreferencesModal';
import { RemoveNodeModal } from './RemoveNodeModal';
import { ResetConfigModal } from './ResetConfigModal';
@@ -43,6 +44,8 @@ const ModalManager = () => {
return ;
case modalRoutes.failSystemRequirements:
return ;
+ case modalRoutes.podman:
+ return ;
case modalRoutes.addValidator:
return null;
case modalRoutes.clientVersions:
diff --git a/src/renderer/Presentational/ModalManager/PodmanModal.tsx b/src/renderer/Presentational/ModalManager/PodmanModal.tsx
new file mode 100644
index 000000000..874bdaf2c
--- /dev/null
+++ b/src/renderer/Presentational/ModalManager/PodmanModal.tsx
@@ -0,0 +1,59 @@
+import { useCallback, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { Modal } from '../../Generics/redesign/Modal/Modal.js';
+import electron from '../../electronGlobal.js';
+import { reportEvent } from '../../events/reportEvent.js';
+import PodmanWrapper from '../PodmanModal/PodmanWrapper.js';
+import { type ModalConfig, modalOnChangeConfig } from './modalUtils.js';
+
+type Props = {
+ modalOnClose: () => void;
+};
+
+export const PodmanModal = ({ modalOnClose }: Props) => {
+ const [modalConfig, setModalConfig] = useState({});
+ const [isSaveButtonDisabled, setIsSaveButtonDisabled] = useState(false);
+ const { t } = useTranslation();
+ const buttonSaveLabel = t('Done');
+
+ const modalOnSaveConfig = async (updatedConfig: ModalConfig | undefined) => {
+ try {
+ console.log('set some kind of setting here?');
+ } catch (err) {
+ console.error(err);
+ throw new Error(
+ 'There was an error removing the node. Try again and please report the error to the NiceNode team in Discord.',
+ );
+ }
+ modalOnClose();
+ };
+
+ const disableSaveButton = useCallback((value: boolean) => {
+ setIsSaveButtonDisabled(value);
+ }, []);
+
+ return (
+
+ {
+ modalOnChangeConfig(
+ config,
+ modalConfig,
+ setModalConfig,
+ save,
+ modalOnSaveConfig,
+ );
+ }}
+ disableSaveButton={disableSaveButton}
+ />
+
+ );
+};
diff --git a/src/renderer/Presentational/ModalManager/PreferencesModal.tsx b/src/renderer/Presentational/ModalManager/PreferencesModal.tsx
index 96215bccc..a53a7d83a 100644
--- a/src/renderer/Presentational/ModalManager/PreferencesModal.tsx
+++ b/src/renderer/Presentational/ModalManager/PreferencesModal.tsx
@@ -82,6 +82,7 @@ export const PreferencesModal = ({ modalOnClose }: Props) => {
return (
void;
-};
-
-export const AlphaBuildModal = ({ modalOnClose }: Props) => {
- const buttonSaveLabel = 'I Understand';
-
- const modalOnSaveConfig = async () => {
- await electron.getSetHasSeenAlphaModal(true);
- console.log('save!');
- modalOnClose();
- };
-
- return (
-
-
-
- );
-};
diff --git a/src/renderer/Presentational/ModalManager/modalUtils.tsx b/src/renderer/Presentational/ModalManager/modalUtils.tsx
index b5075a19e..280991e68 100644
--- a/src/renderer/Presentational/ModalManager/modalUtils.tsx
+++ b/src/renderer/Presentational/ModalManager/modalUtils.tsx
@@ -41,7 +41,7 @@ export const modalRoutes = Object.freeze({
updateUnavailable: 'updateUnavailable',
failSystemRequirements: 'failSystemRequirements',
alphaBuild: 'alphaBuild',
- updatePodman: 'updatePodman',
+ podman: 'podman',
});
/* Use this to change config settings, saved temporarily in the modal file with backend apis until it's saved by modalOnSaveConfig
diff --git a/src/renderer/Presentational/NodeRequirements/requirementsChecklistUtil.tsx b/src/renderer/Presentational/NodeRequirements/requirementsChecklistUtil.tsx
index cb330b75e..6267bbbfe 100644
--- a/src/renderer/Presentational/NodeRequirements/requirementsChecklistUtil.tsx
+++ b/src/renderer/Presentational/NodeRequirements/requirementsChecklistUtil.tsx
@@ -14,6 +14,23 @@ import { bytesToGB } from '../../utils';
import type { NodeRequirementsProps } from './NodeRequirements';
import { findSystemStorageDetailsAtALocation } from './nodeStorageUtil';
+const isVersionHigher = (currentVersion: string, targetVersion: string) => {
+ const parseVersion = (version: string) => version.split('.').map(Number);
+
+ const current = parseVersion(currentVersion);
+ const target = parseVersion(targetVersion);
+
+ for (let i = 0; i < Math.max(current.length, target.length); i++) {
+ const currentPart = current[i] || 0;
+ const targetPart = target[i] || 0;
+ if (currentPart > targetPart) return true;
+ if (currentPart < targetPart) return false;
+ }
+ return false;
+};
+
+const TARGET_MACOS_VERSION = '13.0.0';
+
export const makeCheckList = (
{ nodeRequirements, systemData, nodeStorageLocation }: NodeRequirementsProps,
t: TFunction,
@@ -32,6 +49,23 @@ export const makeCheckList = (
}
console.log('nodeLocationStorageDetails', nodeLocationStorageDetails);
+ if (systemData?.os?.platform === 'darwin') {
+ const checkListItem: ChecklistItemProps = {
+ checkTitle: t('macOSTitle', {
+ minVersion: TARGET_MACOS_VERSION,
+ }),
+ valueText: t('macOSDescription', {
+ version: systemData?.os?.release,
+ }),
+ status: '',
+ };
+ if (isVersionHigher(systemData?.os?.release, TARGET_MACOS_VERSION)) {
+ checkListItem.status = 'complete';
+ } else {
+ checkListItem.status = 'error';
+ }
+ newChecklistItems.push(checkListItem);
+ }
for (const [nodeReqKey, nodeReqValue] of Object.entries(nodeRequirements)) {
console.log(`${nodeReqKey}: ${nodeReqValue}`);
if (nodeReqKey === 'documentationUrl' || nodeReqKey === 'description') {
diff --git a/src/renderer/Presentational/PodmanInstallation/PodmanInstallation.tsx b/src/renderer/Presentational/PodmanInstallation/PodmanInstallation.tsx
index 0ecdf58ae..f8b997a79 100644
--- a/src/renderer/Presentational/PodmanInstallation/PodmanInstallation.tsx
+++ b/src/renderer/Presentational/PodmanInstallation/PodmanInstallation.tsx
@@ -31,6 +31,7 @@ import {
learnMore,
titleFont,
} from './podmanInstallation.css';
+import { messageContainer } from './podmanInstallation.css.js';
// 6.5(docker), ? min on 2022 MacbookPro 16inch, baseline
const TOTAL_INSTALL_TIME_SEC = 5 * 60;
@@ -189,9 +190,6 @@ const PodmanInstallation = ({
// react-hooks/exhaustive-deps
// }, []);
- console.log('isPodmanInstalled', isPodmanInstalled);
- console.log('podmanDetails', podmanDetails);
-
// listen to podman install messages
return (