Compare commits

...

10 Commits

Author SHA1 Message Date
Gerald-H
2f29f21519
Update README.md 2025-03-09 03:44:07 +01:00
92917dbeaa Update Fix 2025-03-09 03:18:00 +01:00
55d15cf147 fix 2025-03-09 02:35:50 +01:00
5554024131 fix 2025-03-09 02:28:42 +01:00
e297acdcdc fix 2025-03-09 02:25:28 +01:00
9dd80f704c fix 2025-03-09 02:17:34 +01:00
06cfc8244a Merge remote-tracking branch 'origin/main' 2025-03-09 02:13:33 +01:00
f5c7e607df url fix 2025-03-09 02:13:23 +01:00
Gerald-H
23500f0c20
Update update.json 2025-03-09 02:09:23 +01:00
f751559878 Release 1.0.6
- Responsive design addet
- Crypto News Auto Update without Browser Refresh every 3 minutes
- Crypto Price Refresh Reduce from 5 Second to 1 Second
- Docker command updated to saved alarms and registered cryptocurrencies that are still there after a Docker image update
--> docker run -p 3099:3099 -p 5001:5001 -v hodleye_data:/app/data --name hodleye-container hodleye-crypto-tracker

- Bugfix "Edit List" works now
- Addet Update Notification every 5 Days
- re-Design from Crypto-box
- improving the logic of the price display for small cryptocurrencies
2025-03-09 02:07:54 +01:00
13 changed files with 1360 additions and 926 deletions

View File

@ -1,4 +1,4 @@
MIT License
Copyright (c) 2025 Gerald-H

210
README.md
View File

@ -4,29 +4,27 @@ A lightweight Docker-based web tool to monitor cryptocurrency prices (via Binanc
<img src="https://github.com/user-attachments/assets/d87ca663-97be-4c22-a0ab-46505fe9c99f" width="800" height="auto">
---
## Table of Contents
1. [Overview](#overview)
2. [Features](#features)
- [Unlimited Alarms & Tracking](#unlimited-alarms--tracking)
- [Price Updates](#price-updates)
- [Alarm Functionality](#alarm-functionality)
- [Crypto News](#crypto-news)
- [Economic Calendar](#economic-calendar)
3. [Installation & Usage](#installation--usage)
- [Requirements](#requirements)
- [Docker Build & Run](#docker-build--run)
4. [Windows Notification App: HodlEye_Notify](#windows-notification-app-hodleye_notify)
5. [Project Structure](#project-structure)
- [Frontend (index.html & magic.js)](#frontend-indexhtml--magicjs)
- [News Feed Server (Node.js)](#news-feed-server-nodejs)
6. [Important Notes / Limitations](#important-notes--limitations)
7. [Coming Soon](#coming-soon)
8. [Privacy & Data Disclaimer](#privacy--data-disclaimer)
1. [Overview](#overview)
2. [Features](#features)
- [Unlimited Alarms & Tracking](#unlimited-alarms--tracking)
- [Price Updates](#price-updates)
- [Alarm Functionality](#alarm-functionality)
- [Crypto News](#crypto-news)
- [Economic Calendar](#economic-calendar)
3. [Installation & Usage](#installation--usage)
- [Requirements](#requirements)
- [Docker Build & Run](#docker-build--run)
4. [Windows Notification App: HodlEye_Notify](#windows-notification-app-hodleye_notify)
5. [Project Structure](#project-structure)
- [Frontend (index.html & magic.js)](#frontend-indexhtml--magicjs)
- [News Feed Server (Node.js)](#news-feed-server-nodejs)
6. [Important Notes / Limitations](#important-notes--limitations)
7. [Coming Soon](#coming-soon)
8. [Privacy & Data Disclaimer](#privacy--data-disclaimer)
9. [License](#license)
---
@ -35,30 +33,37 @@ A lightweight Docker-based web tool to monitor cryptocurrency prices (via Binanc
**HodlEye Crypto Price Tracker** is a Dockerized application that aims to surpass typical limitations of other tracking platforms (like TradingView), offering:
- **Unlimited Alarms**: No cap on the number of alarms you can set.
- **Unlimited Crypto Tracking**: Easily add as many coins as you want.
- **Real-Time Price Updates (every 5 seconds)**: Uses Binance and OKX data.
- **Unlimited Alarms**: No cap on the number of alarms you can set.
- **Unlimited Crypto Tracking**: Easily add as many coins as you want.
- **Real-Time Price Updates (every 1 seconds)**: Uses Binance and OKX data.
- **Crypto News & Economic Calendar**: Stay updated on the latest events affecting the market.
The tool refreshes prices every **5 seconds**, which may introduce a slight delay in alarm triggers if the price quickly touches the threshold in between intervals.
The tool refreshes prices every **1 seconds**, which may introduce a slight delay in alarm triggers if the price quickly touches the threshold in between intervals.
---
&nbsp;
## Features
### Unlimited Alarms & Tracking
- You can set **as many alarms as you like** — no daily or total limit.
- Track **any number of cryptocurrencies** in the list simultaneously.
<img src="https://github.com/user-attachments/assets/276de04d-dcf6-411a-b550-cbb5104c1579" width="auto" height="400">
&nbsp;
### Price Updates
- **Binance** and **OKX** are integrated as the primary data sources.
- By default, HodlEye tries Binance first; if that fails or is forced off, it falls back to OKX.
- It automatically fetches the current price, 24h open, 1h open, and calculates the 24h and 1h percentage changes every **5 seconds**.
- It automatically fetches the current price, 24h open, 1h open, and calculates the 24h and 1h percentage changes every **1 seconds**.
&nbsp;
### Alarm Functionality
- Set alarms for each coin (e.g., `BTC/USDT`), choosing:
- **Alarm Price** (threshold)
- **Direction** (Rising, Falling, or Both)
@ -66,94 +71,112 @@ The tool refreshes prices every **5 seconds**, which may introduce a slight dela
- When triggered, a popup and sound notification appear, with optional desktop notifications.
- **Once** alarms are marked locally in the browser (not removed from the server) so they do not trigger again unless reloaded or manually reset.
&nbsp;
### Crypto News
- News from multiple RSS sources:
- `https://crypto.news/feed/`
- `https://cointelegraph.com/rss`
- `https://thedefiant.io/api/feed`
- `https://newsbtc.com/feed`
- `https://news.bitcoin.com/feed` *(may be inaccessible in certain regions)*
- `https://bitcoinmagazine.com/feed`
- `https://cryptopanic.com/news/rss/`
- `https://crypto.news/feed/`
- `https://cointelegraph.com/rss`
- `https://thedefiant.io/api/feed`
- `https://newsbtc.com/feed`
- `https://news.bitcoin.com/feed` _(may be inaccessible in certain regions)_
- `https://bitcoinmagazine.com/feed`
- `https://cryptopanic.com/news/rss/`
- `https://decrypt.co/feed`
- Quickly view and filter recent articles within the built-in News modal.
<img src="https://github.com/user-attachments/assets/f0727b39-a075-4d50-9600-f53c803d4a1b" width="auto" height="400">
&nbsp;
### Economic Calendar
- The **Economic Calendar** button opens a modal with an [Investing.com](https://www.investing.com/) iframe, showing major economic events such as central bank decisions and market-impacting announcements.
<img src="https://github.com/user-attachments/assets/e254301e-9aaa-48d8-84e7-6faa598ca8be" width="600" height="auto">
---
&nbsp;
## Installation & Usage
### Requirements
- [Docker](https://www.docker.com/) installed.
- (Optional) [Docker-Compose](https://docs.docker.com/compose/) if you want a more complex or multi-container setup.
### Docker Build & Run
1. **Clone this repository**
1. **Clone this repository**
```bash
git clone https://github.com/YourGitHubName/HodlEye.git
git clone https://github.com/Gerald-Ha/HodlEye-Crypto-Price-Tracker.git
cd HodlEye
```
2. **Build the Docker image**
2. **Build the Docker image**
```bash
docker buildx build -t hodleye-crypto-tracker .
```
*(Make sure youre in the same directory as the Dockerfile.)*
3. **Run the container**
_(Make sure youre in the same directory as the Dockerfile.)_
3. **Run the container**
```bash
docker run -d -p 3099:3099 -p 5001:5001 --name hodleye-container hodleye-crypto-tracker
docker run -p 3099:3099 -p 5001:5001 -v hodleye_data:/app/data --name hodleye-container hodleye-crypto-tracker
```
- Port `3099` serves the main web interface.
- Port `3099` serves the main web interface.
- Port `5001` is used by the Node.js server that fetches news RSS feeds.
4. **Access the application**
- **Main UI**: [http://localhost:3099](http://localhost:3099)
4. **Access the application**
- **Main UI**: [http://localhost:3099](http://localhost:3099)
- **News Feed Endpoint**: [http://localhost:5001/api/news](http://localhost:5001/api/news)
---
---
&nbsp;
## Windows Notification App: HodlEye_Notify
<img src="https://github.com/user-attachments/assets/a3356708-1b3a-4fb8-9a71-d1af88f29c5f" width="auto" height="150">
<img src="https://github.com/user-attachments/assets/cb0eb2a8-45fb-4a80-bb81-56957e838153" width="auto" height="150">
If you prefer not to keep the HodlEye Crypto Price Tracker web interface open in your browser all the time, you can use a lightweight Windows application called **HodlEye_Notify**. This tool connects directly to the same endpoint as the Docker container and will display notifications on your desktop whenever an alarm is triggered.
If you prefer not to keep the HodlEye Crypto Price Tracker web interface open in your browser all the time, you can use a lightweight Windows application called **HodlEye_Notify**. This tool connects directly to the same endpoint as the Docker container and will display notifications on your desktop whenever an alarm is triggered.
&nbsp;
1. **Setup**
1. **Setup**
- Enter the IP address and port of your HodlEye Docker container (for example, `http://192.168.1.112:3099/`) in the HodlEye_Notify window.
- Click **Connect** to establish a WebSocket connection.
- Once connected, youll see the status change to “Connected.”
- Enter the IP address and port of your HodlEye Docker container (for example, `http://192.168.1.112:3099/`) in the HodlEye_Notify window.
- Click **Connect** to establish a WebSocket connection.
- Once connected, youll see the status change to “Connected.”
2. **Autostart**
- Add HodlEye_Notify to your Windows **Startup** folder so it automatically launches when Windows starts. This way, youll continuously receive notifications without needing to reopen the program manually.
2. **Autostart**
3. **Testing Notifications**
- From the machine running the Docker container, you can trigger a test notification using the following `curl` command:
```bash
- Add HodlEye_Notify to your Windows **Startup** folder so it automatically launches when Windows starts. This way, youll continuously receive notifications without needing to reopen the program manually.
3. **Testing Notifications**
- From the machine running the Docker container, you can trigger a test notification using the following `curl` command:
**Ubuntu**
```bash
curl -X POST http://192.168.1.112:3099/api/notifications \
-H "Content-Type: application/json" \
-d "{\"message\": \"⚠️ ALARM (Recurring, Both): BTC reached 92250\", \"timestamp\": \"2025-03-06T06:19:58.584Z\"}"
```
- If everything is configured correctly, you should receive a desktop notification from HodlEye_Notify indicating the alarm has triggered.
```
**Windows CMD**
```bash
curl -X POST http://192.168.1.112:3099/api/notifications -H "Content-Type: application/json" -d "{\"message\": \"⚠️ ALARM (Recurring, Both): BTC reached 92250\", \"timestamp\": \"2025-03-06T06:19:58.584Z\"}"
```
- If everything is configured correctly, you should receive a desktop notification from HodlEye_Notify indicating the alarm has triggered.
This application simplifies the process of staying informed about your alarms, letting you work on other tasks without leaving the HodlEye web interface open.
---
&nbsp;
## Project Structure
Below is an example directory tree (based on your structure). Yours may vary slightly:
@ -191,24 +214,28 @@ HodlEye-Crypto-Price-Tracker
└── ping.mp3
```
&nbsp;
### Frontend (`index.html` & `magic.js`)
- **`index.html`**
- Main interface containing modals and buttons (Add Crypto, Edit List, Alarms, Options, etc.).
- **`index.html`**
- Main interface containing modals and buttons (Add Crypto, Edit List, Alarms, Options, etc.).
- Includes buttons for:
- **Crypto News** (opens a news modal)
- **Economic Calendar** (Investing.com iframe)
- **Unlimited Alarms** management
- **Options** (Dark mode, alarm sounds, desktop notifications)
- **Crypto News** (opens a news modal)
- **Economic Calendar** (Investing.com iframe)
- **Unlimited Alarms** management
- **Options** (Dark mode, alarm sounds, desktop notifications)
- **Buy me a Coffee** donation button
- **`magic.js`**
- **`magic.js`**
- Core logic:
- Fetches cryptos (`/api/cryptos`)
- Loads alarms (`/api/alarms`) and notifications (`/api/notifications`)
- Pulls prices from Binance/OKX every 5 seconds
- Checks and triggers alarms
- Fetches cryptos (`/api/cryptos`)
- Loads alarms (`/api/alarms`) and notifications (`/api/notifications`)
- Pulls prices from Binance/OKX every 1 seconds
- Checks and triggers alarms
- Handles UI rendering (prices, alarms, notifications, drag & drop reorder)
&nbsp;
### News Feed Server (Node.js)
- A minimal Node.js Express server (in `server.js` or similar) which:
- Retrieves the listed RSS feeds and parses them via `xml2js`
- Serves them in JSON format at `/api/news`
@ -221,51 +248,60 @@ HodlEye-Crypto-Price-Tracker
```
- Accessible at [http://localhost:5001/api/news](http://localhost:5001/api/news).
*(Within Docker, its already bundled, so just expose `5001`.)*
_(Within Docker, its already bundled, so just expose `5001`.)_
---
&nbsp;
## Important Notes / Limitations
1. **5-second polling**
- Theres a potential delay in alarms because price thresholds are only checked every 5 seconds. If a price briefly touches and moves away between polls, you might miss that exact trigger moment.
1. **1-second polling**
2. **API availability**
- Binance/OKX may be temporarily down or might not support certain symbols.
- Theres a potential delay in alarms because price thresholds are only checked every 1 seconds. If a price briefly touches and moves away between polls, you might miss that exact trigger moment.
2. **API availability**
- Binance/OKX may be temporarily down or might not support certain symbols.
- HodlEye tries Binance → fallback to OKX if needed.
3. **Unlimited Alarms (Once vs. Recurring)**
- **Once** alarms become locally “triggered” to avoid repeated alerts but are not server-side deactivated.
3. **Unlimited Alarms (Once vs. Recurring)**
- **Once** alarms become locally “triggered” to avoid repeated alerts but are not server-side deactivated.
- **Recurring** triggers repeatedly every time the threshold is crossed.
4. **RSS Feeds**
- Some feeds might be inaccessible in certain regions (`bitcoin_news` in Germany, for instance).
4. **RSS Feeds**
- Some feeds might be inaccessible in certain regions (`bitcoin_news` in Germany, for instance).
- Economic Calendar content is loaded from an `<iframe>` pointing to Investing.com.
---
&nbsp;
## Coming Soon
Exciting new features and improvements are on the way! Here are some planned updates:
- **Portfolio Management**: Track your crypto holdings in real-time with easy-to-read analytics.
- **Portfolio Management**: Track your crypto holdings in real-time with easy-to-read analytics.
- **More Integrations**: Expanding support for additional exchanges and data sources.
- **Automatic alarm in the event of a sharp price drop of 10% within 1 hour**
- **HodlEye Notify Alarm with various sound selections and HodlEye Alarms**
- **Adding more Responsive design**
Stay tuned for updates!
---
---
&nbsp;
## Privacy & Data Disclaimer
- **No Data Collection by This Application**: HodlEye itself does not collect, store, or process any personal data or usage analytics.
- **External Services**: Certain features (e.g., news feeds, iframes) rely on third-party websites or APIs. We do not control and are not responsible for the data-collection practices or privacy policies of these external providers. Please refer to the privacy policies of those services for details.
- **No Data Collection by This Application**: HodlEye itself does not collect, store, or process any personal data or usage analytics.
- **External Services**: Certain features (e.g., news feeds, iframes) rely on third-party websites or APIs. We do not control and are not responsible for the data-collection practices or privacy policies of these external providers. Please refer to the privacy policies of those services for details.
---
&nbsp;

8
data/data.json Normal file
View File

@ -0,0 +1,8 @@
{
"cryptos": [
"BTC",
"ETH"
],
"alarms": [],
"notifications": []
}

View File

@ -0,0 +1 @@
<svg version="1.1" id="main_outline" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" style="enable-background:new 0 0 640 640;" xml:space="preserve" viewBox="5.67 143.05 628.65 387.55"> <g> <path id="teabag" style="fill:#FFFFFF" d="M395.9,484.2l-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5,21.2-17.9,33.8-11.8 c17.2,8.3,27.1,13,27.1,13l-0.1-109.2l16.7-0.1l0.1,117.1c0,0,57.4,24.2,83.1,40.1c3.7,2.3,10.2,6.8,12.9,14.4 c2.1,6.1,2,13.1-1,19.3l-61,126.9C423.6,484.9,408.4,490.3,395.9,484.2z"></path> <g> <g> <path style="fill:#609926" d="M622.7,149.8c-4.1-4.1-9.6-4-9.6-4s-117.2,6.6-177.9,8c-13.3,0.3-26.5,0.6-39.6,0.7c0,39.1,0,78.2,0,117.2 c-5.5-2.6-11.1-5.3-16.6-7.9c0-36.4-0.1-109.2-0.1-109.2c-29,0.4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5 c-9.8-0.6-22.5-2.1-39,1.5c-8.7,1.8-33.5,7.4-53.8,26.9C-4.9,212.4,6.6,276.2,8,285.8c1.7,11.7,6.9,44.2,31.7,72.5 c45.8,56.1,144.4,54.8,144.4,54.8s12.1,28.9,30.6,55.5c25,33.1,50.7,58.9,75.7,62c63,0,188.9-0.1,188.9-0.1s12,0.1,28.3-10.3 c14-8.5,26.5-23.4,26.5-23.4s12.9-13.8,30.9-45.3c5.5-9.7,10.1-19.1,14.1-28c0,0,55.2-117.1,55.2-231.1 C633.2,157.9,624.7,151.8,622.7,149.8z M125.6,353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6,321.8,60,295.4 c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5,38.5-30c13.8-3.7,31-3.1,31-3.1s7.1,59.4,15.7,94.2c7.2,29.2,24.8,77.7,24.8,77.7 S142.5,359.9,125.6,353.9z M425.9,461.5c0,0-6.1,14.5-19.6,15.4c-5.8,0.4-10.3-1.2-10.3-1.2s-0.3-0.1-5.3-2.1l-112.9-55 c0,0-10.9-5.7-12.8-15.6c-2.2-8.1,2.7-18.1,2.7-18.1L322,273c0,0,4.8-9.7,12.2-13c0.6-0.3,2.3-1,4.5-1.5c8.1-2.1,18,2.8,18,2.8 l110.7,53.7c0,0,12.6,5.7,15.3,16.2c1.9,7.4-0.5,14-1.8,17.2C474.6,363.8,425.9,461.5,425.9,461.5z"></path> <path style="fill:#609926" d="M326.8,380.1c-8.2,0.1-15.4,5.8-17.3,13.8c-1.9,8,2,16.3,9.1,20c7.7,4,17.5,1.8,22.7-5.4 c5.1-7.1,4.3-16.9-1.8-23.1l24-49.1c1.5,0.1,3.7,0.2,6.2-0.5c4.1-0.9,7.1-3.6,7.1-3.6c4.2,1.8,8.6,3.8,13.2,6.1 c4.8,2.4,9.3,4.9,13.4,7.3c0.9,0.5,1.8,1.1,2.8,1.9c1.6,1.3,3.4,3.1,4.7,5.5c1.9,5.5-1.9,14.9-1.9,14.9 c-2.3,7.6-18.4,40.6-18.4,40.6c-8.1-0.2-15.3,5-17.7,12.5c-2.6,8.1,1.1,17.3,8.9,21.3c7.8,4,17.4,1.7,22.5-5.3 c5-6.8,4.6-16.3-1.1-22.6c1.9-3.7,3.7-7.4,5.6-11.3c5-10.4,13.5-30.4,13.5-30.4c0.9-1.7,5.7-10.3,2.7-21.3 c-2.5-11.4-12.6-16.7-12.6-16.7c-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3c4.7-9.7,9.4-19.3,14.1-29 c-4.1-2-8.1-4-12.2-6.1c-4.8,9.8-9.7,19.7-14.5,29.5c-6.7-0.1-12.9,3.5-16.1,9.4c-3.4,6.3-2.7,14.1,1.9,19.8 C343.2,346.5,335,363.3,326.8,380.1z"></path> </g> </g> </g> </svg>

After

(image error) Size: 2.5 KiB

View File

@ -0,0 +1 @@
<svg width="98" height="96" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#24292f"/></svg>

After

(image error) Size: 963 B

View File

@ -1,258 +1,295 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>HodlEye Crypto Price Tracker</title>
<link rel="stylesheet" href="style.css">
<link rel="icon" type="image/png" href="images/favicon.png">
</head>
<body class="dark">
<link rel="stylesheet" href="style.css" />
<link rel="stylesheet" href="responsive.css" />
<link rel="icon" type="image/png" href="images/favicon.png" />
</head>
<body class="dark">
<div class="header">
<h1>HodlEye Crypto Price Tracker</h1>
<div class="button-grid-container">
<div class="grid-left">
<button onclick="openCryptoNews()">Crypto News</button>
<button onclick="openEconomicCalendar()">Economic Calendar</button>
</div>
<div class="grid-middle">
<button onclick="openAddCryptoModal()">Add Crypto</button>
<button onclick="toggleEditMode()" id="editButton">Edit List</button>
<button class="alarm-btn" onclick="openAlarmModal()">Alarms⏰</button>
<button class="options-btn" onclick="openOptionsModal()">Options</button>
</div>
<div class="grid-right">
</div>
<h1>HodlEye Crypto Price Tracker</h1>
<div class="button-grid-container">
<div class="grid-left">
<button onclick="openCryptoNews()">Crypto News</button>
<button onclick="openEconomicCalendar()">Economic Calendar</button>
</div>
<div class="grid-middle">
<button onclick="openAddCryptoModal()">Add Crypto</button>
<button onclick="toggleEditMode()" id="editButton">Edit List</button>
<button class="alarm-btn" onclick="openAlarmModal()">Alarms</button>
<button class="options-btn" onclick="openOptionsModal()">
Options
</button>
</div>
<div class="grid-right"></div>
</div>
</div>
<div class="main-container">
<div class="left-column">
<div class="grid-container" id="cryptoGrid">
</div>
<div class="left-column">
<div class="grid-container" id="cryptoGrid"></div>
</div>
<div class="right-column">
<div class="notify-area">
<div class="notify-heading">
<span>Notify</span>
<button onclick="clearNotifications()">Clear List</button>
</div>
<ul class="notify-list" id="notifyList"></ul>
</div>
<div class="right-column">
<div class="notify-heading">
<span>Notify</span>
<button onclick="clearNotifications()">Clear List</button>
</div>
<ul class="notify-list" id="notifyList">
</ul>
<div class="buttons-and-version">
<button class="coffee-btn" onclick="openBuyMeModal()">
Buy me a Coffee
<img src="images/coffee.svg" alt="Coffee" class="coffee-icon" />
</button>
<button class="coffee-btn" onclick="openBuyMeModal()">
Buy me a Coffee <img src="images/coffee.svg" alt="Coffee" class="coffee-icon">
</button>
<button
class="my-button"
onclick="window.open('https://github.com/Gerald-Ha/HodlEye-Crypto-Price-Tracker', '_blank')"
>
Github
<img
src="images/github-mark.svg"
alt="Git Logo"
class="coffee-icon"
/>
</button>
<button onclick="window.open('https://www.gerald-hasani.com', '_blank')">Developer</button>
<div class="version-info">
<span id="currentVersion">Version 1.0.6</span>
<span
id="updateAvailable"
style="display: none; color: red; cursor: pointer"
>
Update Available
</span>
</div>
</div>
</div>
</div>
<div id="alarmModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeAlarmModal()">&times;</span>
<h2>Price Alarms</h2>
<div class="modal-content">
<span class="close" onclick="closeAlarmModal()">&times;</span>
<h2>Price Alarms</h2>
<div class="alarm-list-container" id="alarmListContainer">
</div>
<div class="alarm-list-container" id="alarmListContainer"></div>
<label for="alarmSymbol">Symbol:</label>
<select id="alarmSymbol"></select><br>
<label for="alarmSymbol">Symbol:</label>
<select id="alarmSymbol"></select
><br />
<label for="alarmPrice">Alarm Price (USDT):</label>
<input type="number" id="alarmPrice" placeholder="Price"><br>
<label for="alarmPrice">Alarm Price (USDT):</label>
<input type="number" id="alarmPrice" placeholder="Price" /><br />
<label for="alarmFrequency">Frequency:</label>
<select id="alarmFrequency">
<option value="Once">Once</option>
<option value="Recurring">Recurring</option>
</select><br>
<label for="alarmFrequency">Frequency:</label>
<select id="alarmFrequency">
<option value="Once">Once</option>
<option value="Recurring">Recurring</option></select
><br />
<label for="alarmDirection">Direction:</label>
<select id="alarmDirection">
<option value="Rising">Rising</option>
<option value="Falling">Falling</option>
<option value="Both">Both</option>
</select><br>
<button onclick="addAlarm()">Add Alarm</button>
</div>
<label for="alarmDirection">Direction:</label>
<select id="alarmDirection">
<option value="Rising">Rising</option>
<option value="Falling">Falling</option>
<option value="Both">Both</option></select
><br />
<button onclick="addAlarm()">Add Alarm</button>
</div>
</div>
<div id="optionsModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeOptionsModal()">&times;</span>
<h2>Options</h2>
<div class="modal-content">
<span class="close" onclick="closeOptionsModal()">&times;</span>
<h2>Options</h2>
<label for="soundSelect">Alarm Sound:</label>
<select id="soundSelect">
<option value="ping.mp3">ping.mp3</option>
<option value="cashing.mp3">cashing.mp3</option>
</select>
<br><br>
<label for="soundSelect">Alarm Sound:</label>
<select id="soundSelect">
<option value="ping.mp3">ping.mp3</option>
<option value="cashing.mp3">cashing.mp3</option>
</select>
<br /><br />
<input type="checkbox" id="darkModeToggle">
<label for="darkModeToggle">Enable Dark Mode</label>
<br><br>
<input type="checkbox" id="darkModeToggle" />
<label for="darkModeToggle">Enable Dark Mode</label>
<br /><br />
<input type="checkbox" id="desktopNotifyToggle">
<label for="desktopNotifyToggle">Enable Desktop Notifications</label>
<br><br>
<input type="checkbox" id="desktopNotifyToggle" />
<label for="desktopNotifyToggle">Enable Desktop Notifications</label>
<br /><br />
<button onclick="openApiModal()">ADD/Edit OKX API</button>
<br><br>
<button onclick="openApiModal()">ADD/Edit OKX API</button>
<br /><br />
<button onclick="saveOptions()">Save</button>
</div>
<button onclick="saveOptions()">Save</button>
</div>
</div>
<div id="apiModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeApiModal()">&times;</span>
<h2>OKX API Settings</h2>
<p>Enter your OKX API data here (optional). Primary is Binance.
If Binance fails, the site tries OKX (or if you force OKX).</p>
<div class="modal-content">
<span class="close" onclick="closeApiModal()">&times;</span>
<h2>OKX API Settings</h2>
<p>
Enter your OKX API data here (optional). Primary is Binance. If
Binance fails, the site tries OKX (or if you force OKX).
</p>
<label for="okxApiKey">OKX API-Key:</label><br>
<input type="text" id="okxApiKey" placeholder="e.g. c42166aa-...">
<br>
<label for="okxApiKey">OKX API-Key:</label><br />
<input type="text" id="okxApiKey" placeholder="e.g. c42166aa-..." />
<br />
<label for="okxSecretKey">OKX Secret-Key:</label><br>
<input type="text" id="okxSecretKey" placeholder="e.g. ACD0B07F...">
<br>
<label for="okxSecretKey">OKX Secret-Key:</label><br />
<input type="text" id="okxSecretKey" placeholder="e.g. ACD0B07F..." />
<br />
<label for="okxPassphrase">OKX Passphrase (if needed):</label><br>
<input type="text" id="okxPassphrase" placeholder="(optional)">
<br><br>
<label for="okxPassphrase">OKX Passphrase (if needed):</label><br />
<input type="text" id="okxPassphrase" placeholder="(optional)" />
<br /><br />
<button onclick="saveApiSettings()">Save</button>
</div>
<button onclick="saveApiSettings()">Save</button>
</div>
</div>
<div id="addCryptoModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeAddCryptoModal()">&times;</span>
<h2>Add a New Cryptocurrency</h2>
<label for="newCryptoSymbol">Symbol (e.g. ETH):</label>
<input type="text" id="newCryptoSymbol" placeholder="BTC, ETH, ADA..."><br>
<br><br>
<button onclick="addNewCrypto()">Add</button>
</div>
<div class="modal-content">
<span class="close" onclick="closeAddCryptoModal()">&times;</span>
<h2>Add a New Cryptocurrency</h2>
<label for="newCryptoSymbol">Symbol (e.g. ETH):</label>
<input
type="text"
id="newCryptoSymbol"
placeholder="BTC, ETH, ADA..."
/><br />
<br /><br />
<button onclick="addNewCrypto()">Add</button>
</div>
</div>
<div id="apiSelectModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeApiSelectModal()">&times;</span>
<h2>API Selection for <span id="apiSelectSymbol"></span></h2>
<label for="apiSelectDropdown">Please choose:</label>
<select id="apiSelectDropdown">
<option value="auto">Auto (Binance -> OKX fallback)</option>
<option value="binance">Force Binance</option>
<option value="okx">Force OKX</option>
</select>
<br><br>
<button onclick="saveApiSelection()">Save</button>
</div>
<div class="modal-content">
<span class="close" onclick="closeApiSelectModal()">&times;</span>
<h2>API Selection for <span id="apiSelectSymbol"></span></h2>
<label for="apiSelectDropdown">Please choose:</label>
<select id="apiSelectDropdown">
<option value="auto">Auto (Binance -> OKX fallback)</option>
<option value="binance">Force Binance</option>
<option value="okx">Force OKX</option>
</select>
<br /><br />
<button onclick="saveApiSelection()">Save</button>
</div>
</div>
<div id="buyMeModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeBuyMeModal()">&times;</span>
<h2>Buy me a Coffee</h2>
<p><strong>Send me:</strong> USDT / Ethereum</p>
<p><strong>Chain:</strong>
<span style="text-decoration: underline; cursor: pointer;"
onclick="copyToClipboard('0x26c2E3F6C854Af006520ec2ce433982866bB7632')">
ETH
</span> /
<span style="text-decoration: underline; cursor: pointer;"
onclick="copyToClipboard('0x26c2E3F6C854Af006520ec2ce433982866bB7632')">
BSC
</span>
</p>
<p>
<span style="text-decoration: underline; cursor: pointer;"
onclick="copyToClipboard('0x26c2E3F6C854Af006520ec2ce433982866bB7632')">
0x26c2E3F6C854Af006520ec2ce433982866bB7632
</span>
</p>
<p>(When clicked, the address is copied to the clipboard.)</p>
</div>
<div class="modal-content">
<span class="close" onclick="closeBuyMeModal()">&times;</span>
<h2>Buy me a Coffee</h2>
<p><strong>Send me:</strong> USDT / Ethereum</p>
<p>
<strong>Chain:</strong>
<span
style="text-decoration: underline; cursor: pointer"
onclick="copyToClipboard('0x26c2E3F6C854Af006520ec2ce433982866bB7632')"
>
ETH
</span>
/
<span
style="text-decoration: underline; cursor: pointer"
onclick="copyToClipboard('0x26c2E3F6C854Af006520ec2ce433982866bB7632')"
>
BSC
</span>
</p>
<p>
<span
style="text-decoration: underline; cursor: pointer"
onclick="copyToClipboard('0x26c2E3F6C854Af006520ec2ce433982866bB7632')"
>
0x26c2E3F6C854Af006520ec2ce433982866bB7632
</span>
</p>
<p>(When clicked, the address is copied to the clipboard.)</p>
</div>
</div>
<div id="alarmOverlay" class="alert-overlay">
<div class="alert-box">
<div id="alarmMessage"></div>
<button onclick="closeAlarmPopup()">Close</button>
</div>
<div class="alert-box">
<div id="alarmMessage"></div>
<button onclick="closeAlarmPopup()">Close</button>
</div>
</div>
<div id="errorOverlay" class="error-overlay">
<div class="error-box">
<p id="errorMessage"></p>
<button onclick="closeErrorPopup()">Ok</button>
</div>
<div class="error-box">
<p id="errorMessage"></p>
<button onclick="closeErrorPopup()">Ok</button>
</div>
</div>
<audio id="alarmSound">
<source src="sound/ping.mp3" type="audio/mpeg">
<source src="sound/ping.mp3" type="audio/mpeg" />
</audio>
<div id="cryptoNewsModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeCryptoNewsModal()">&times;</span>
<h2>Crypto News</h2>
<input type="text" id="search" placeholder="Suche nach Artikeln...">
<input type="text" id="search" placeholder="Suche nach Artikeln..." />
<div id="news-feed">Lade News...</div>
</div>
</div>
<div id="economicCalendarModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeEconomicCalendarModal()">&times;</span>
<span class="close" onclick="closeEconomicCalendarModal()"
>&times;</span
>
<h2>Economic Calendar</h2>
<div class="iframe-container">
<div class="iframe-wrapper">
<iframe
src="https://sslecal2.investing.com?columns=exc_flags,exc_currency,exc_importance,exc_actual,exc_forecast,exc_previous&features=datepicker,timezone&countries=25,32,6,37,72,22,17,39,14,10,35,43,56,36,110,11,26,12,4,5&calType=week&timeZone=8&lang=1"
allowtransparency="true">
<div class="iframe-wrapper">
<iframe
src="https://sslecal2.investing.com?columns=exc_flags,exc_currency,exc_importance,exc_actual,exc_forecast,exc_previous&features=datepicker,timezone&countries=25,32,6,37,72,22,17,39,14,10,35,43,56,36,110,11,26,12,4,5&calType=week&timeZone=8&lang=1"
allowtransparency="true"
>
</iframe>
</div>
<div class="poweredBy">
<span>Real Time Economic Calendar provided by
<a href="https://www.investing.com/" rel="nofollow" target="_blank" class="underline_link">Investing.com</a>.
</span>
</div>
</div>
<div class="poweredBy">
<span
>Real Time Economic Calendar provided by
<a
href="https://www.investing.com/"
rel="nofollow"
target="_blank"
class="underline_link"
>Investing.com</a
>.
</span>
</div>
</div>
</div>
</div>
<div id="updateModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeUpdateModal()">&times;</span>
<h2 id="updateTitle">Update auf <span id="updateVersion"></span></h2>
<p id="updateChanges"></p>
<button onclick="performUpdate()">Update</button>
</div>
</div>
<script src="magic.js"></script>
<script src="news.js"></script>
</body>
<script src="update.js"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -4,17 +4,21 @@ document.addEventListener("DOMContentLoaded", function () {
const searchInput = document.getElementById("search");
let allArticles = [];
fetch(apiUrl)
.then(response => response.json())
.then(data => {
allArticles = data;
displayArticles(allArticles);
})
.catch(error => {
newsFeed.innerHTML = "Error loading the news.";
console.error("Error when retrieving the news:", error);
});
function refreshNewsFeed() {
fetch(apiUrl)
.then(response => response.json())
.then(data => {
allArticles = data;
displayArticles(allArticles);
})
.catch(error => {
newsFeed.innerHTML = "Error loading the news.";
console.error("Error when retrieving the news:", error);
});
}
function displayArticles(items) {
newsFeed.innerHTML = "";
items.forEach(item => {
@ -30,11 +34,11 @@ document.addEventListener("DOMContentLoaded", function () {
</div>
</div>
`;
newsFeed.appendChild(newsItem);
});
}
searchInput.addEventListener("input", function () {
const searchTerm = searchInput.value.toLowerCase();
const filteredArticles = allArticles.filter(item =>
@ -44,6 +48,7 @@ document.addEventListener("DOMContentLoaded", function () {
displayArticles(filteredArticles);
});
function getTimeAgo(date) {
const now = new Date();
const seconds = Math.floor((now - new Date(date)) / 1000);
@ -59,6 +64,7 @@ document.addEventListener("DOMContentLoaded", function () {
return `vor ${Math.floor(days / 365)} Jahren`;
}
function formatSourceName(source) {
const sourceMap = {
"crypto_news": "Crypto News",
@ -70,4 +76,8 @@ document.addEventListener("DOMContentLoaded", function () {
};
return sourceMap[source] || source;
}
refreshNewsFeed();
setInterval(refreshNewsFeed, 180000);
});

74
public/responsive.css Normal file
View File

@ -0,0 +1,74 @@
@media (max-width: 1614px) {
.grid-container {
grid-template-columns: repeat(4, 1fr);
}
}
@media (max-width: 1353px) {
.grid-container {
grid-template-columns: repeat(3, 1fr);
}
}
@media (max-width: 1275px) {
.grid-container {
grid-template-columns: repeat(3, 1fr);
}
}
@media (max-width: 1200px) {
.grid-container {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 992px) {
.grid-container {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 768px) {
.grid-container {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 480px) {
.grid-container {
grid-template-columns: 1fr;
}
}
.main-container {
display: flex;
}
.left-column {
width: 75%;
}
.right-column {
width: 320px;
border-left: 1px solid #444;
padding: 20px;
display: flex;
flex-direction: column;
}
@media (max-width: 1251px) {
.button-grid-container {
display: grid;
grid-template-columns: none;
align-items: center;
justify-items: center;
column-gap: 10px;
margin-bottom: 15px;
}
}

View File

@ -1,12 +1,13 @@
body {
font-family: Arial, sans-serif;
margin: 0 auto; /* Zentrierung */
margin: 0 auto;
padding: 0;
transition: background-color 0.4s, color 0.4s;
min-width: 1400px;
max-width: 1978px;
max-width: 1778px;
min-width: 808px;
}
h1 {
font-size: 28px;
}
@ -16,6 +17,7 @@ body.dark {
background-color: #121212;
color: #ffffff;
}
body.light {
background-color: #f2f2f2;
color: #000000;
@ -26,9 +28,11 @@ body.light {
text-align: center;
padding: 20px;
}
.button-container {
margin-bottom: 10px;
}
button {
padding: 10px;
margin: 5px;
@ -38,31 +42,68 @@ button {
border: none;
cursor: pointer;
border-radius: 5px;
}
button:hover {
background-color: #005f73;
}
.alarm-btn { background-color: #ff8b00; }
.alarm-btn:hover { background-color: #e38806; }
.options-btn { background-color: #555; }
.options-btn:hover { background-color: #777; }
.alarm-btn {
background-color: #ff8b00;
}
.alarm-btn:hover {
background-color: #e38806;
}
.options-btn {
background-color: #555;
}
.options-btn:hover {
background-color: #777;
}
.main-container {
display: flex;
}
.left-column {
width: 75%;
padding: 20px;
padding: 10px;
}
.right-column {
width: 320px;
display: grid;
grid-template-rows: 300px auto;
border-left: 1px solid #444;
width: 320px;
padding: 20px;
}
.notify-area {
min-height: 400px;
max-width: 280px;
border: 1px solid #444;
overflow-y: auto;
padding: 10px;
display: flex;
flex-direction: column;
}
.buttons-and-version {
display: flex;
flex-direction: column;
max-width: 260px;
padding-left: 6%;
}
.notify-heading {
display: flex;
@ -71,6 +112,7 @@ button:hover {
font-size: 20px;
margin-bottom: 10px;
}
.notify-list {
list-style: none;
padding-left: 0;
@ -83,12 +125,14 @@ button:hover {
.grid-container {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-columns: repeat(5, 1fr);
gap: 10px;
width: 90%;
width: 80%;
margin: auto;
padding: 10px;
}
.crypto-box {
background-color: #1e1e1e;
border: 2px solid #444;
@ -98,15 +142,22 @@ button:hover {
font-size: 16px;
position: relative;
user-select: none;
min-width: 177px;
}
body.light .crypto-box {
background-color: #ffffff;
border: 2px solid #ccc;
color: #000;
}
.change.up { color: #00ff00; }
.change.down { color: #ff0000; }
.change.up {
color: #00ff00;
}
.change.down {
color: #ff0000;
}
.delete-btn {
@ -125,6 +176,7 @@ body.light .crypto-box {
line-height: 24px;
padding: 0;
}
.delete-btn:hover {
background-color: #999;
color: #000;
@ -135,6 +187,7 @@ body.light .crypto-box {
opacity: 0.5;
transform: scale(0.97);
}
.crypto-box.drag-over {
border: 2px dashed #ccc !important;
}
@ -144,49 +197,52 @@ body.light .crypto-box {
display: none;
position: fixed;
z-index: 2;
left: 0; top: 0;
width: 100%; height: 100%;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
}
.modal-content {
background-color: #222;
background-color: #222222f2;
margin: 5% auto;
padding: 20px;
border-radius: 8px;
text-align: center;
border-radius: 8px;
max-width: 40%;
text-align: center;
color: white;
overflow-y: auto;
overflow-x: hidden;
position: relative;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.3);
position: relative;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.3);
}
#cryptoNewsModal .modal-content {
width: 40%;
overflow-x: hidden;
overflow-y: auto;
max-height: 80vh;
overflow-y: auto;
max-height: 80vh;
}
#buyMeModal .modal-content {
width: 30%;
overflow-x: hidden;
text-align: center;
text-align: center;
position: absolute;
top: 35%;
left: 50%;
transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
align-items: center;
justify-content: center;
}
@ -199,28 +255,28 @@ body.light .crypto-box {
}
#optionsModal .modal-content {
width: 20%;
width: 30%;
overflow-x: hidden;
}
#addCryptoModal .modal-content {
width: 20%;
text-align: center;
width: 20%;
text-align: center;
position: absolute;
top: 35%;
left: 50%;
transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
align-items: center;
justify-content: center;
}
#addCryptoModal .modal-content label {
display: block;
margin-bottom: 6px;
margin-bottom: 6px;
}
#addCryptoModal .close {
@ -235,43 +291,44 @@ body.light .crypto-box {
#addCryptoModal .modal-content select,
#addCryptoModal .modal-content input {
width: 100%;
height: 20px;
font-size: 16px;
width: 100%;
height: 20px;
font-size: 16px;
padding: 8px;
border: 1px solid #ccc;
box-sizing: border-box;
border: 1px solid #ccc;
box-sizing: border-box;
}
#alarmModal .modal-content {
width: 30%;
text-align: center;
min-width: 406px;
text-align: center;
position: absolute;
top: 35%;
left: 50%;
transform: translate(-50%, -50%);
transform: translate(-50%, -45%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
align-items: center;
justify-content: center;
}
#alarmModal .modal-content label {
display: block;
margin-bottom: 3px;
margin-bottom: 3px;
}
#alarmModal .modal-content select,
#alarmModal .modal-content input {
margin-bottom: 8px;
width: 100%;
margin-bottom: 8px;
width: 30%;
padding: 8px;
}
#alarmModal .modal-content select {
padding: 8px;
padding: 8px;
font-size: 13px;
width: 100%;
width: 30%;
}
#alarmModal .close {
@ -289,17 +346,23 @@ body.light .modal-content {
background-color: #ddd;
color: #000;
}
.close {
float: right;
font-size: 24px;
cursor: pointer;
}
label {
display: inline-block;
margin-top: 10px;
}
select, input[type="number"], input[type="text"] {
select,
input[type="number"],
input[type="text"] {
margin: 5px 0 10px 5px;
font-size: medium;
}
@ -307,10 +370,13 @@ select, input[type="number"], input[type="text"] {
display: none;
position: fixed;
z-index: 3;
left: 0; top: 0;
width: 100%; height: 100%;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
}
.alert-box {
background-color: #333;
color: #fff;
@ -321,13 +387,16 @@ select, input[type="number"], input[type="text"] {
text-align: center;
position: relative;
}
.alert-box button {
margin-top: 15px;
background-color: #555;
}
.alert-box button:hover {
background-color: #777;
}
body.light .alert-box {
background-color: #ccc;
color: #000;
@ -338,10 +407,13 @@ body.light .alert-box {
display: none;
position: fixed;
z-index: 4;
left: 0; top: 0;
width: 100%; height: 100%;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
}
.error-box {
background-color: #aa3333;
color: #fff;
@ -352,6 +424,7 @@ body.light .alert-box {
text-align: center;
position: relative;
}
body.light .error-box {
background-color: #f2aaaa;
color: #000;
@ -365,6 +438,7 @@ body.light .error-box {
text-decoration: underline;
cursor: pointer;
}
body.light .api-label {
color: #333;
}
@ -378,6 +452,7 @@ body.light .api-label {
overflow-y: auto;
margin: 1em 0;
}
.alarm-item {
display: flex;
align-items: center;
@ -386,10 +461,12 @@ body.light .api-label {
padding: 6px;
border-radius: 3px;
}
body.light .alarm-item {
background-color: #eee;
color: #000;
}
.alarm-delete-btn {
background-color: #008CBA;
border: none;
@ -398,6 +475,7 @@ body.light .alarm-item {
border-radius: 5px;
padding: 5px 8px;
}
.alarm-delete-btn:hover {
background-color: #005f73;
}
@ -412,17 +490,19 @@ body.light .alarm-item {
.coffee-btn {
margin-top: 60px;
background-color: #614484;
font-family: 'Bree Serif', serif;
background-color: #614484;
font-family: 'Bree Serif', serif;
font-size: 16px;
color: white;
padding: 10px 20px;
border: none;
cursor: pointer;
}
.coffee-btn:hover {
background-color: #711fe9;
}
.coffee-icon {
width: 25px;
height: 25px;
@ -443,21 +523,26 @@ body.light .alarm-item {
color: white;
text-align: center;
}
#news-feed {
text-align: left;
}
.news-item {
padding: 15px;
border-bottom: 1px solid #333;
transition: background 0.3s;
}
.news-item:last-child {
border-bottom: none;
}
.news-item:hover {
background: rgba(255, 255, 255, 0.1);
cursor: pointer;
}
.news-title {
font-size: 18px;
color: #1db954;
@ -465,6 +550,7 @@ body.light .alarm-item {
display: block;
margin-bottom: 5px;
}
.news-meta {
font-size: 14px;
color: gray;
@ -472,10 +558,12 @@ body.light .alarm-item {
justify-content: space-between;
align-items: center;
}
.news-source {
font-weight: bold;
color: #ffffff;
}
.time {
font-size: 14px;
color: gray;
@ -484,18 +572,14 @@ body.light .alarm-item {
.button-grid-container {
display: grid;
grid-template-columns: 1fr 2fr 1fr;
grid-template-columns: 1fr 1fr 1fr;
/* alle gleich breit */
align-items: center;
justify-items: center;
gap: 10px;
column-gap: 10px;
/* oder kleiner */
margin-bottom: 15px;
}
.grid-left, .grid-middle, .grid-right {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 10px;
}
#economicCalendarModal .iframe-container {
@ -510,6 +594,7 @@ body.light .alarm-item {
align-items: center;
justify-content: center;
}
#economicCalendarModal .iframe-wrapper {
position: relative;
width: 100%;
@ -520,12 +605,14 @@ body.light .alarm-item {
background: white;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.2);
}
#economicCalendarModal iframe {
width: calc(100% - 20px);
height: calc(100% - 20px);
border: none;
display: block;
}
#economicCalendarModal .poweredBy {
margin-top: 10px;
font-family: Arial, Helvetica, sans-serif;
@ -533,6 +620,7 @@ body.light .alarm-item {
color: #ffffff;
text-align: center;
}
#economicCalendarModal .poweredBy a {
color: #06529D;
font-weight: bold;
@ -540,3 +628,26 @@ body.light .alarm-item {
}
#updateModal .modal-content {}
.version-info {
text-align: center;
margin-top: 10px;
}
.change {
margin: 5px;
}
.my-button {
background-color: white;
color: black;
border: 1px solid black;
padding: 8px 16px;
cursor: pointer;
font-weight: 700;
}

112
public/update.js Normal file
View File

@ -0,0 +1,112 @@
const CURRENT_VERSION = "1.0.6";
function getUpdateUrl() {
return "/api/update?t=" + new Date().getTime();
}
document.addEventListener("DOMContentLoaded", () => {
document.getElementById("currentVersion").textContent = `Version ${CURRENT_VERSION}`;
checkForUpdates();
setInterval(checkForUpdates, 259200000);
});
function checkForUpdates() {
fetch(getUpdateUrl())
.then((response) => response.json())
.then((data) => {
let skippedVersion = localStorage.getItem("skippedVersion") || null;
if (skippedVersion && data.version !== skippedVersion) {
localStorage.removeItem("skippedVersion");
skippedVersion = null;
}
if (compareVersions(data.version, CURRENT_VERSION) > 0) {
const updateAvailableEl = document.getElementById("updateAvailable");
updateAvailableEl.style.display = "inline";
updateAvailableEl.onclick = () => {
fetch(getUpdateUrl())
.then((response) => response.json())
.then((data) => {
let skippedVersion = localStorage.getItem("skippedVersion") || null;
if (skippedVersion && data.version !== skippedVersion) {
localStorage.removeItem("skippedVersion");
skippedVersion = null;
}
openUpdateModal(data);
})
.catch((error) =>
console.error("Fehler beim erneuten Abrufen des Updates:", error)
);
};
}
})
.catch((error) =>
console.error("Fehler beim Abrufen des Updates:", error)
);
}
function compareVersions(v1, v2) {
const v1parts = v1.split(".").map(Number);
const v2parts = v2.split(".").map(Number);
for (let i = 0; i < Math.max(v1parts.length, v2parts.length); i++) {
const num1 = v1parts[i] || 0;
const num2 = v2parts[i] || 0;
if (num1 > num2) return 1;
if (num1 < num2) return -1;
}
return 0;
}
function openUpdateModal(data) {
document.getElementById("updateVersion").textContent = data.version;
let changelogContainer = document.getElementById("updateChanges");
changelogContainer.innerHTML = "";
if (Array.isArray(data.changelog)) {
let ul = document.createElement("ul");
data.changelog.forEach(item => {
let li = document.createElement("li");
li.textContent = item;
ul.appendChild(li);
});
changelogContainer.appendChild(ul);
} else {
changelogContainer.textContent = data.changelog || "Kein Changelog vorhanden.";
}
window._updateData = data;
document.getElementById("updateModal").style.display = "block";
}
function closeUpdateModal() {
document.getElementById("updateModal").style.display = "none";
}
function performUpdate() {
window.open("https://github.com/Gerald-Ha/HodlEye-Crypto-Price-Tracker", "_blank");
closeUpdateModal();
}
function skipUpdate() {
if (window._updateData && window._updateData.version) {
localStorage.setItem("skippedVersion", window._updateData.version);
}
closeUpdateModal();
}

View File

@ -5,6 +5,7 @@ const cors = require("cors");
const http = require("http");
const WebSocket = require("ws");
const app = express();
app.use(cors());
app.use(express.json());
@ -16,7 +17,7 @@ const clients = new Set();
/*
* Metadata
* Version: 1.0.5
* Version: 1.0.6
* Author/Dev: Gerald Hasani
* Name: HodlEye Crypto Price Tracker
* Email: contact@gerald-hasani.com
@ -25,12 +26,20 @@ const clients = new Set();
const DATA_FILE = path.join(__dirname, "..", "data", "data.json");
if (!fs.existsSync(DATA_FILE)) {
fs.writeFileSync(DATA_FILE, JSON.stringify({
cryptos: ["BTC"],
alarms: [],
notifications: []
}, null, 2));
fs.writeFileSync(
DATA_FILE,
JSON.stringify(
{
cryptos: ["BTC"],
alarms: [],
notifications: []
},
null,
2
)
);
}
function readData() {
@ -41,6 +50,23 @@ function writeData(data) {
fs.writeFileSync(DATA_FILE, JSON.stringify(data, null, 2));
}
app.get("/api/update", (req, res) => {
const remoteUpdateUrl = "https://raw.githubusercontent.com/Gerald-Ha/HodlEye-Crypto-Price-Tracker/refs/heads/main/update.json";
fetch(remoteUpdateUrl)
.then(response => response.json())
.then(data => {
res.header("Cache-Control", "no-cache, no-store, must-revalidate");
res.header("Pragma", "no-cache");
res.header("Expires", "0");
res.json(data);
})
.catch(error => {
console.error("Fehler beim Abrufen der remote update.json:", error);
res.status(500).json({ error: "Could not fetch update data" });
});
});
app.get("/api/cryptos", (req, res) => {
const data = readData();
res.json(data.cryptos);
@ -51,10 +77,8 @@ app.post("/api/cryptos", (req, res) => {
if (!symbol) {
return res.status(400).json({ error: "Symbol is required." });
}
const data = readData();
const upperSymbol = symbol.toUpperCase();
if (!data.cryptos.includes(upperSymbol)) {
data.cryptos.push(upperSymbol);
writeData(data);
@ -70,6 +94,17 @@ app.delete("/api/cryptos/:symbol", (req, res) => {
res.json({ success: true, cryptos: data.cryptos });
});
app.put("/api/cryptos", (req, res) => {
const { cryptoList } = req.body;
if (!Array.isArray(cryptoList)) {
return res.status(400).json({ error: "cryptoList must be an array." });
}
const data = readData();
data.cryptos = cryptoList;
writeData(data);
res.json({ success: true, cryptos: data.cryptos });
});
app.get("/api/alarms", (req, res) => {
const data = readData();
res.json(data.alarms);
@ -80,7 +115,6 @@ app.post("/api/alarms", (req, res) => {
if (!symbol || !price) {
return res.status(400).json({ error: "symbol and price are required." });
}
const data = readData();
const newAlarm = {
id: Date.now(),
@ -113,7 +147,6 @@ app.post("/api/notifications", (req, res) => {
if (!message) {
return res.status(400).json({ error: "message is required." });
}
const data = readData();
const entry = {
message,
@ -122,7 +155,7 @@ app.post("/api/notifications", (req, res) => {
data.notifications.unshift(entry);
writeData(data);
// Nachricht an alle WebSocket-Clients senden
clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(entry));
@ -141,18 +174,15 @@ app.delete("/api/notifications", (req, res) => {
app.use(express.static(path.join(__dirname, "..", "public")));
// WebSocket-Server für Echtzeitbenachrichtigungen
wss.on("connection", (ws) => {
console.log("Client verbunden");
clients.add(ws);
ws.on("close", () => {
console.log("Client getrennt");
clients.delete(ws);
});
console.log("Client verbunden");
clients.add(ws);
ws.on("close", () => {
console.log("Client getrennt");
clients.delete(ws);
});
});
// Server starten
const PORT = process.env.PORT || 3099;
server.listen(PORT, () => {
console.log(`Server läuft auf Port ${PORT}`);

14
update.json Normal file
View File

@ -0,0 +1,14 @@
{
"version": "1.0.6",
"changelog": [
"Bug fixes and improvements",
"Responsive design added",
"Crypto News Auto Update without Browser Refresh every 3 minutes",
"Crypto Price Refresh Reduce from 5 Second to 1 Second",
"Docker command updated to save alarms and registered cryptocurrencies that are still there after a Docker image update (--> docker run -p 3099:3099 -p 5001:5001 -v hodleye_data:/app/data --name hodleye-container hodleye-crypto-tracker)",
"Bugfix 'Edit List' works now",
"Added Update Option and Notification every 5 Days",
"Re-Design from Crypto-box",
"Improving the logic of the price display for small cryptocurrencies"
]
}