From 1746ababa4609d517af9615ea04a94bb81ef558c Mon Sep 17 00:00:00 2001 From: Kittisak Phormraksa Date: Tue, 30 Apr 2024 15:56:38 +0700 Subject: [PATCH] Update from original 2024-04-30 (#6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Adds automatic extention of nest stream before it expires. * Updated README with more accurate information regarding nest integration. * Added support to stream backchannel to a command (outputbc) * Code Cleanup, rename outputbc to execbc, using buffered Writer * execbc-source: Merged the dial function to the Client creation * execbc: increased Buffer Size for IO Operation * execbc: Removed Buffered IO since it caused delay in the audio output * fix(log-display): reverse log order to display newest first The The applyLogStyling function in log.html has been updated to reverse the array of log lines. After parsing the JSON data, reversing the array ensures that the most recent logs appear at the top of the list. This change enhances the readability for users by displaying the logs in a descending chronological order. * fix(clipboard): fix copy to clipboard functionality Added a `copyTextToClipboard` function to handle text copying across different browsers and fallback scenarios. This function utilizes the Clipboard API when available, providing an asynchronous method to copy text securely. For browsers where the Clipboard API is not available or the page is not served over a secure context, a fallback method using a temporary textarea element and `document.execCommand` is employed. Replaced direct use of `navigator.clipboard.writeText` with this function in the 'shareget' click event listener to enhance cross-browser support and error handling. * feat: Add signal related params to exec * Update build.yml * Added FreeBSD Binaries (#2) Co-authored-by: Rob van Oostenrijk * Updated FreeBSD ffmpeg integrations * Initial commit * fix grammar Co-authored-by: Felipe Santos * pkg/hap/camera/accessory.go * Fix crash with tapo cameras not returning 201 * feat(app): support daemon mode on non-Windows platforms Added a new command-line flag `-daemon` to run the application in the background as a daemon. This option is only available for non-Windows operating systems due to platform-specific process handling. When enabled, the application restarts itself with the same arguments except for the `-daemon` flag, prints the PID of the background process, and then exits the current process. * fix(daemon-mode): handle '-daemon' argument correctly for background execution This commit fixes the issue where the '-daemon' argument was not being properly handled when re-executing the program in daemon mode. The loop removes the '-daemon' flag from the arguments slice before the program is re-run in the background, ensuring that subsequent executions do not attempt to enter daemon mode again. The change will prevent potential errors or unexpected behavior due to the presence of the '-daemon' argument in recursive calls, making the daemon mode feature more robust and reliable. * ci: upgrade GitHub Actions to newer versions Updated various GitHub Actions used in the CI workflows (build.yml, gh-pages.yml, test.yml) to their latest major versions. This includes actions for checking out code, setting up Go, uploading artifacts, configuring Docker, and deploying to GitHub Pages. The update is part of routine maintenance to ensure compatibility with the latest features and improvements provided by these actions. * Modified func Close in pkg/isapi/client.go to call '/ISAPI/System/TwoWayAudio/channels//close' instead of '/ISAPI/System/TwoWayAudio/channels//close' instead of '/ISAPI/System/TwoWayAudio/channels/ Co-authored-by: Michael Reif <19reifl93@gmail.com> Co-authored-by: Sergey Krashevich Co-authored-by: dadav <33197631+dadav@users.noreply.github.com> Co-authored-by: Rob van Oostenrijk Co-authored-by: Rob van Oostenrijk Co-authored-by: Felipe Santos Co-authored-by: civita <14911217+civita@users.noreply.github.com> Co-authored-by: Josip Janzic Co-authored-by: f1d094 Co-authored-by: pabst2k <54397544+pabst2k@users.noreply.github.com> Co-authored-by: April MacDonald Co-authored-by: Евгений Co-authored-by: Alex X <511909+AlexxIT@users.noreply.github.com> Co-authored-by: Gennaro Gallo Co-authored-by: Alex X Co-authored-by: Jono Gould <78023819+jgould-godaddy@users.noreply.github.com> Co-authored-by: Pattarapon Theanthong --- .github/workflows/build.yml | 64 +++--- .github/workflows/gh-pages.yml | 8 +- .github/workflows/test.yml | 14 +- .gitignore | 4 +- README.md | 42 ++-- assets/logo.png | Bin 0 -> 38124 bytes hardware.Dockerfile | 3 +- internal/api/api.go | 22 +- internal/app/app.go | 23 +- internal/exec/exec.go | 34 ++- internal/exec/pipe.go | 36 ++- internal/ffmpeg/device/device_freebsd.go | 97 +++++++++ internal/ffmpeg/hardware/hardware_freebsd.go | 60 +++++ internal/mp4/mp4.go | 3 +- internal/streams/helpers.go | 3 + internal/streams/stream.go | 9 +- internal/webrtc/client.go | 16 +- internal/webrtc/milestone.go | 218 +++++++++++++++++++ pkg/hap/camera/accessory.go | 1 + pkg/isapi/client.go | 13 +- pkg/ivideon/client.go | 6 + pkg/mdns/syscall_freebsd.go | 24 ++ pkg/nest/api.go | 85 +++++++- pkg/nest/client.go | 5 +- pkg/shell/shell.go | 19 -- pkg/stdin/client.go | 41 ++++ pkg/stdin/consumer.go | 52 +++++ pkg/stdin/pipe.go | 26 +++ pkg/tapo/client.go | 17 +- pkg/tcp/request.go | 4 +- pkg/webrtc/track.go | 34 ++- www/add.html | 27 --- www/editor.html | 6 +- www/index.html | 88 ++++---- www/links.html | 31 ++- www/log.html | 66 +++--- www/main.js | 148 ++++++++++++- 37 files changed, 1126 insertions(+), 223 deletions(-) create mode 100644 assets/logo.png create mode 100644 internal/ffmpeg/device/device_freebsd.go create mode 100644 internal/ffmpeg/hardware/hardware_freebsd.go create mode 100644 internal/webrtc/milestone.go create mode 100644 pkg/mdns/syscall_freebsd.go create mode 100644 pkg/stdin/client.go create mode 100644 pkg/stdin/consumer.go create mode 100644 pkg/stdin/pipe.go diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e709fad72..f0293f3e6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,99 +15,113 @@ jobs: env: { CGO_ENABLED: 0 } steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: { go-version: '1.21' } - name: Build go2rtc_win64 env: { GOOS: windows, GOARCH: amd64 } run: go build -ldflags "-s -w" -trimpath - name: Upload go2rtc_win64 - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: { name: go2rtc_win64, path: go2rtc.exe } - name: Build go2rtc_win32 env: { GOOS: windows, GOARCH: 386 } run: go build -ldflags "-s -w" -trimpath - name: Upload go2rtc_win32 - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: { name: go2rtc_win32, path: go2rtc.exe } - name: Build go2rtc_win_arm64 env: { GOOS: windows, GOARCH: arm64 } run: go build -ldflags "-s -w" -trimpath - name: Upload go2rtc_win_arm64 - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: { name: go2rtc_win_arm64, path: go2rtc.exe } - name: Build go2rtc_linux_amd64 env: { GOOS: linux, GOARCH: amd64 } run: go build -ldflags "-s -w" -trimpath - name: Upload go2rtc_linux_amd64 - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: { name: go2rtc_linux_amd64, path: go2rtc } - name: Build go2rtc_linux_i386 env: { GOOS: linux, GOARCH: 386 } run: go build -ldflags "-s -w" -trimpath - name: Upload go2rtc_linux_i386 - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: { name: go2rtc_linux_i386, path: go2rtc } - name: Build go2rtc_linux_arm64 env: { GOOS: linux, GOARCH: arm64 } run: go build -ldflags "-s -w" -trimpath - name: Upload go2rtc_linux_arm64 - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: { name: go2rtc_linux_arm64, path: go2rtc } - name: Build go2rtc_linux_arm env: { GOOS: linux, GOARCH: arm, GOARM: 7 } run: go build -ldflags "-s -w" -trimpath - name: Upload go2rtc_linux_arm - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: { name: go2rtc_linux_arm, path: go2rtc } - name: Build go2rtc_linux_armv6 env: { GOOS: linux, GOARCH: arm, GOARM: 6 } run: go build -ldflags "-s -w" -trimpath - name: Upload go2rtc_linux_armv6 - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: { name: go2rtc_linux_armv6, path: go2rtc } - name: Build go2rtc_linux_mipsel env: { GOOS: linux, GOARCH: mipsle } run: go build -ldflags "-s -w" -trimpath - name: Upload go2rtc_linux_mipsel - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: { name: go2rtc_linux_mipsel, path: go2rtc } - name: Build go2rtc_mac_amd64 env: { GOOS: darwin, GOARCH: amd64 } run: go build -ldflags "-s -w" -trimpath - name: Upload go2rtc_mac_amd64 - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: { name: go2rtc_mac_amd64, path: go2rtc } - name: Build go2rtc_mac_arm64 env: { GOOS: darwin, GOARCH: arm64 } run: go build -ldflags "-s -w" -trimpath - name: Upload go2rtc_mac_arm64 - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: { name: go2rtc_mac_arm64, path: go2rtc } + - name: Build go2rtc_freebsd_amd64 + env: { GOOS: freebsd, GOARCH: amd64 } + run: go build -ldflags "-s -w" -trimpath + - name: Upload go2rtc_freebsd_amd64 + uses: actions/upload-artifact@v3 + with: { name: go2rtc_freebsd_amd64, path: go2rtc } + + - name: Build go2rtc_freebsd_arm64 + env: { GOOS: freebsd, GOARCH: arm64 } + run: go build -ldflags "-s -w" -trimpath + - name: Upload go2rtc_freebsd_arm64 + uses: actions/upload-artifact@v3 + with: { name: go2rtc_freebsd_arm64, path: go2rtc } + docker-master: name: Build docker master runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Docker meta id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: ${{ github.repository }} tags: | @@ -116,20 +130,20 @@ jobs: type=match,pattern=v(.*),group=1 - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Login to DockerHub if: github.event_name != 'pull_request' - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: context: . platforms: | @@ -148,11 +162,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Docker meta id: meta-hw - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: ${{ github.repository }} flavor: | @@ -164,20 +178,20 @@ jobs: type=match,pattern=v(.*),group=1 - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Login to DockerHub if: github.event_name != 'pull_request' - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: context: . file: hardware.Dockerfile diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index f3d85c4d2..4d0e2e670 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -25,13 +25,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Pages - uses: actions/configure-pages@v3 + uses: actions/configure-pages@v4 - name: Upload artifact - uses: actions/upload-pages-artifact@v1 + uses: actions/upload-pages-artifact@v3 with: path: './website' - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v2 + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a98a83e53..b23faf535 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,10 +21,10 @@ jobs: GOARCH: ${{ matrix.arch }} steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v5 with: go-version: '1.21' @@ -70,13 +70,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Build and push - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: context: . platforms: linux/${{ matrix.platform }} @@ -89,7 +89,7 @@ jobs: - name: Build and push Hardware if: matrix.platform == 'amd64' - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: context: . file: hardware.Dockerfile diff --git a/.gitignore b/.gitignore index d948344ed..c97dd7cea 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,6 @@ go2rtc.json 0_test.go -.goreload \ No newline at end of file +.DS_Store + +.goreload diff --git a/README.md b/README.md index aaed9410c..3bfe3a483 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,12 @@ -# go2rtc +

-[![](https://img.shields.io/github/stars/AlexxIT/go2rtc?style=flat-square&logo=github)](https://github.com/AlexxIT/go2rtc/stargazers) -[![](https://img.shields.io/docker/pulls/alexxit/go2rtc?style=flat-square&logo=docker&logoColor=white&label=pulls)](https://hub.docker.com/r/alexxit/go2rtc) -[![](https://img.shields.io/github/downloads/AlexxIT/go2rtc/total?color=blue&style=flat-square&logo=github)](https://github.com/AlexxIT/go2rtc/releases) -[![](https://goreportcard.com/badge/github.com/AlexxIT/go2rtc)](https://goreportcard.com/report/github.com/AlexxIT/go2rtc) + ![go2rtc](assets/logo.png) +
+ [![stars](https://img.shields.io/github/stars/AlexxIT/go2rtc?style=flat-square&logo=github)](https://github.com/AlexxIT/go2rtc/stargazers) + [![docker pulls](https://img.shields.io/docker/pulls/alexxit/go2rtc?style=flat-square&logo=docker&logoColor=white&label=pulls)](https://hub.docker.com/r/alexxit/go2rtc) + [![releases](https://img.shields.io/github/downloads/AlexxIT/go2rtc/total?color=blue&style=flat-square&logo=github)](https://github.com/AlexxIT/go2rtc/releases) + [![goreport](https://goreportcard.com/badge/github.com/AlexxIT/go2rtc)](https://goreportcard.com/report/github.com/AlexxIT/go2rtc) +

Ultimate camera streaming application with support RTSP, WebRTC, HomeKit, FFmpeg, RTMP, etc. @@ -34,6 +37,7 @@ Ultimate camera streaming application with support RTSP, WebRTC, HomeKit, FFmpeg - [GStreamer](https://gstreamer.freedesktop.org/) framework pipeline idea - [MediaSoup](https://mediasoup.org/) framework routing idea - HomeKit Accessory Protocol from [@brutella](https://github.com/brutella/hap) +- creator of the project's logo [@v_novoseltsev](https://www.instagram.com/v_novoseltsev) --- @@ -146,13 +150,13 @@ Container [alexxit/go2rtc](https://hub.docker.com/r/alexxit/go2rtc) with support Latest, but maybe unstable version: -- Binary: GitHub > [Actions](https://github.com/AlexxIT/go2rtc/actions) > [Build and Push](https://github.com/AlexxIT/go2rtc/actions/workflows/build.yml) > latest run > Artifacts section (you should be logged in to GitHub) +- Binary: [latest nightly release](https://nightly.link/AlexxIT/go2rtc/workflows/build/master) - Docker: `alexxit/go2rtc:master` or `alexxit/go2rtc:master-hardware` versions - Hass Add-on: `go2rtc master` or `go2rtc master hardware` versions ## Configuration -- by default go2rtc will search `go2rtc.yaml` in the current work dirrectory +- by default go2rtc will search `go2rtc.yaml` in the current work directory - `api` server will start on default **1984 port** (TCP) - `rtsp` server will start on default **8554 port** (TCP) - `webrtc` will use port **8555** (TCP/UDP) for connections @@ -213,6 +217,7 @@ Supported for sources: - [TP-Link Tapo](#source-tapo) cameras - [Hikvision ISAPI](#source-isapi) cameras - [Roborock vacuums](#source-roborock) models with cameras +- [Exec](#source-exec) audio on server - [Any Browser](#incoming-browser) as IP-camera Two way audio can be used in browser with [WebRTC](#module-webrtc) technology. The browser will give access to the microphone only for HTTPS sites ([read more](https://stackoverflow.com/questions/52759992/how-to-access-camera-and-microphone-in-chrome-without-https)). @@ -230,7 +235,7 @@ streams: amcrest_doorbell: - rtsp://username:password@192.168.1.123:554/cam/realmonitor?channel=1&subtype=0#backchannel=0 unifi_camera: rtspx://192.168.1.123:7441/fD6ouM72bWoFijxK - glichy_camera: ffmpeg:rstp://username:password@192.168.1.123/live/ch00_1 + glichy_camera: ffmpeg:rtsp://username:password@192.168.1.123/live/ch00_1 ``` **Recommendations** @@ -265,7 +270,7 @@ streams: #### Source: RTMP -You can get stream from RTMP server, for example [Frigate](https://docs.frigate.video/configuration/rtmp). +You can get stream from RTMP server, for example [Nginx with nginx-rtmp-module](https://github.com/arut/nginx-rtmp-module). ```yaml streams: @@ -403,20 +408,30 @@ Exec source can run any external application and expect data from it. Two transp If you want to use **RTSP** transport - the command must contain the `{output}` argument in any place. On launch, it will be replaced by the local address of the RTSP server. -**pipe** reads data from app stdout in different formats: **MJPEG**, **H.264/H.265 bitstream**, **MPEG-TS**. +**pipe** reads data from app stdout in different formats: **MJPEG**, **H.264/H.265 bitstream**, **MPEG-TS**. Also pipe can write data to app stdin in two formats: **PCMA** and **PCM/48000**. The source can be used with: - [FFmpeg](https://ffmpeg.org/) - go2rtc ffmpeg source just a shortcut to exec source +- [FFplay](https://ffmpeg.org/ffplay.html) - play audio on your server - [GStreamer](https://gstreamer.freedesktop.org/) - [Raspberry Pi Cameras](https://www.raspberrypi.com/documentation/computers/camera_software.html) - any your own software +Pipe commands support parameters (format: `exec:{command}#{param1}#{param2}`): + +- `killsignal` - signal which will be send to stop the process (numeric form) +- `killtimeout` - time in seconds for forced termination with sigkill +- `backchannel` - enable backchannel for two-way audio + ```yaml streams: stream: exec:ffmpeg -re -i /media/BigBuckBunny.mp4 -c copy -rtsp_transport tcp -f rtsp {output} picam_h264: exec:libcamera-vid -t 0 --inline -o - picam_mjpeg: exec:libcamera-vid -t 0 --codec mjpeg -o - + canon: exec:gphoto2 --capture-movie --stdout#killsignal=2#killtimeout=5 + play_pcma: exec:ffplay -fflags nobuffer -f alaw -ar 8000 -i -#backchannel=1 + play_pcm48k: exec:ffplay -fflags nobuffer -f s16be -ar 48000 -i -#backchannel=1 ``` #### Source: Echo @@ -579,7 +594,7 @@ streams: Any cameras in WebRTC format are supported. But at the moment Home Assistant only supports some [Nest](https://www.home-assistant.io/integrations/nest/) cameras in this fomat. -The Nest API only allows you to get a link to a stream for 5 minutes. So every 5 minutes the stream will be reconnected. +**Important.** The Nest API only allows you to get a link to a stream for 5 minutes. Do not use this with Frigate! If the stream expires, Frigate will consume all available ram on your machine within seconds. It's recommended to use [Nest source](#source-nest) - it supports extending the stream. ```yaml streams: @@ -610,7 +625,7 @@ streams: *[New in v1.6.0](https://github.com/AlexxIT/go2rtc/releases/tag/v1.6.0)* -Currently only WebRTC cameras are supported. Stream reconnects every 5 minutes. +Currently only WebRTC cameras are supported. For simplicity, it is recommended to connect the Nest/WebRTC camera to the [Home Assistant](#source-hass). But if you can somehow get the below parameters - Nest/WebRTC source will work without Hass. @@ -640,7 +655,7 @@ This source type support four connection formats. **whep** -[WebRTC/WHEP](https://www.ietf.org/id/draft-murillo-whep-02.html) - is an unapproved standard for WebRTC video/audio viewers. But it may already be supported in some third-party software. It is supported in go2rtc. +[WebRTC/WHEP](https://datatracker.ietf.org/doc/draft-murillo-whep/) - is replaced by [WebRTC/WISH](https://datatracker.ietf.org/doc/charter-ietf-wish/02/) standard for WebRTC video/audio viewers. But it may already be supported in some third-party software. It is supported in go2rtc. **go2rtc** @@ -1352,6 +1367,7 @@ streams: **Distributions** - [Alpine Linux](https://pkgs.alpinelinux.org/packages?name=go2rtc) +- [Arch User Repository](https://linux-packages.com/aur/package/go2rtc) - [Gentoo](https://github.com/inode64/inode64-overlay/tree/main/media-video/go2rtc) - [NixOS](https://search.nixos.org/packages?query=go2rtc) - [Proxmox Helper Scripts](https://tteck.github.io/Proxmox/) diff --git a/assets/logo.png b/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..2f5bab6f8625a6774f5e94eb234d6f5564aa3f98 GIT binary patch literal 38124 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�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 diff --git a/www/editor.html b/www/editor.html index c24e7f953..8ace08818 100644 --- a/www/editor.html +++ b/www/editor.html @@ -4,7 +4,7 @@ File Editor - + @@ -54,6 +50,7 @@
+

@@ -85,15 +82,27 @@ .replace(/\n/g, '
'); } + const reverseBtn = document.getElementById('reverse'); + const update = document.getElementById('update'); + + let reverseOrder = false; + let autoUpdateEnabled = true; + + reverseBtn.textContent = `Reverse Log Order: ${reverseOrder ? 'ON' : 'OFF'}`; + update.textContent = `Auto Update: ${autoUpdateEnabled ? 'ON' : 'OFF'}`; + function applyLogStyling(jsonlines) { const KEYS = ['time', 'level', 'message']; - const lines = JSON.parse('[' + jsonlines.trimEnd().replaceAll('\n', ',') + ']'); + let lines = JSON.parse('[' + jsonlines.trimEnd().replaceAll('\n', ',') + ']'); + if (reverseOrder) { + lines = lines.reverse(); + } return lines.map(line => { const ts = new Date(line['time']); const msg = Object.keys(line).reduce((msg, key) => { return KEYS.indexOf(key) < 0 ? `${msg} ${key}=${line[key]}` : msg; }, line['message']); - return ``; + return ``; }).join(''); } @@ -112,19 +121,24 @@ reload(); - // Handle auto-update switch - let autoUpdateEnabled = true; - - const update = document.getElementById('update'); + update.textContent = `Auto Update: ${autoUpdateEnabled ? 'ON' : 'OFF'}`; update.addEventListener('click', () => { autoUpdateEnabled = !autoUpdateEnabled; update.textContent = `Auto Update: ${autoUpdateEnabled ? 'ON' : 'OFF'}`; }); + // Toggle log order + reverseBtn.textContent = `Reverse Log Order: ${reverseOrder ? 'ON' : 'OFF'}`; + reverseBtn.addEventListener('click', () => { + reverseOrder = !reverseOrder; + reverseBtn.textContent = `Reverse Log Order: ${reverseOrder ? 'ON' : 'OFF'}`; + reload(); // Reload logs to apply the new order + }); + // Reload the logs every 5 seconds setInterval(() => { if (autoUpdateEnabled) reload(); }, 5000); - \ No newline at end of file + diff --git a/www/main.js b/www/main.js index 63dbde322..2c15e0712 100644 --- a/www/main.js +++ b/www/main.js @@ -18,7 +18,6 @@ i { nav { display: block; - /*width: 660px;*/ margin: 0 auto 10px; } @@ -41,6 +40,97 @@ nav a:hover { nav li { display: inline; } + +body { + font-family: Arial, Helvetica, sans-serif; + background-color: white; +} +table { + background-color: white; + text-align: left; + border-collapse: collapse; +} +table thead { + background: #CFCFCF; + background: linear-gradient(to bottom, #dbdbdb 0%, #d3d3d3 66%, #CFCFCF 100%); + border-bottom: 3px solid black; +} +table thead th { + font-size: 15px; + font-weight: bold; + color: black; + text-align: center; +} +table td, table th { + border: 1px solid black; + padding: 5px 5px; +} + +/* Dark mode styles */ +body.dark-mode { + background-color: #121212; + color: #e0e0e0; +} + +body.dark-mode nav ul { + background: #333; +} + +body.dark-mode a { + background: rgba(45, 45, 45, .8); + border-right: 1px solid #2c2c2c; + color: #c7c7c7; +} + +body.dark-mode a:hover { + background: #555; +} + +body.dark-mode a:visited { + color: #999; +} + +body.dark-mode table { + background-color: #222; + color: #ddd; +} + +body.dark-mode table thead { + background: linear-gradient(to bottom, #444 0%, #3d3d3d 66%, #333 100%); + border-bottom: 3px solid #888; +} +body.dark-mode table thead th { + font-size: 15px; + font-weight: bold; + color: #ddd; + text-align: center; +} +body.dark-mode table td, body.dark-mode table th { + border: 1px solid #444; +} + +body.dark-mode button { + background: rgba(255, 255, 255, .1); + border: 1px solid #444; + color: #ccc; +} + +body.dark-mode input, +body.dark-mode select, +body.dark-mode textarea { + background-color: #333; + color: #e0e0e0; + border: 1px solid #444; +} + +body.dark-mode input::placeholder, +body.dark-mode textarea::placeholder { + color: #bbb; +} + +body.dark-mode hr { + border-top: 1px solid #444; +} ` + document.body.innerHTML; + +const sunIcon = '☀️'; +const moonIcon = '🌕'; + +document.addEventListener('DOMContentLoaded', () => { + const darkModeToggle = document.getElementById('darkModeToggle'); + const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)'); + + const isDarkModeEnabled = () => document.body.classList.contains('dark-mode'); + + // Update the toggle button based on the dark mode state + const updateToggleButton = () => { + if (isDarkModeEnabled()) { + darkModeToggle.innerHTML = sunIcon; + darkModeToggle.setAttribute('aria-label', 'Enable light mode'); + } else { + darkModeToggle.innerHTML = moonIcon; + darkModeToggle.setAttribute('aria-label', 'Enable dark mode'); + } + }; + + const updateDarkMode = () => { + if (localStorage.getItem('darkMode') === 'enabled' || prefersDarkScheme.matches && localStorage.getItem('darkMode') !== 'disabled') { + document.body.classList.add('dark-mode'); + } else { + document.body.classList.remove('dark-mode'); + } + updateEditorTheme(); + updateToggleButton(); + }; + + // Update the editor theme based on the dark mode state + const updateEditorTheme = () => { + if (typeof editor !== 'undefined') { + editor.setTheme(isDarkModeEnabled() ? 'ace/theme/tomorrow_night_eighties' : 'ace/theme/github'); + } + }; + + // Initial update for dark mode and toggle button + updateDarkMode(); + + // Listen for changes in the system's color scheme preference + prefersDarkScheme.addEventListener('change', updateDarkMode); // Modern approach + + // Toggle dark mode and update local storage on button click + darkModeToggle.addEventListener('click', () => { + const enabled = document.body.classList.toggle('dark-mode'); + localStorage.setItem('darkMode', enabled ? 'enabled' : 'disabled'); + updateToggleButton(); // Update the button after toggling + updateEditorTheme(); + }); +});
${ts.toLocaleString()}${line['level']}${escapeHTML(msg)}
${ts.toLocaleString()}${escapeHTML(line['level'])}${escapeHTML(msg)}