mirror of
https://github.com/lone-cloud/prism
synced 2026-06-03 08:43:10 -07:00
code clean ups, minor improvements
This commit is contained in:
parent
2a7b1e91c6
commit
ea3345825a
20 changed files with 215 additions and 294 deletions
1
.github/workflows/release-dev.yml
vendored
1
.github/workflows/release-dev.yml
vendored
|
|
@ -30,5 +30,4 @@ jobs:
|
|||
push: true
|
||||
build-args: |
|
||||
VERSION=dev
|
||||
COMMIT=${{ github.sha }}
|
||||
tags: ghcr.io/lone-cloud/prism:dev
|
||||
|
|
|
|||
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
|
|
@ -26,18 +26,17 @@ jobs:
|
|||
- name: Build binaries
|
||||
run: |
|
||||
VERSION=$(cat VERSION)
|
||||
COMMIT=$(git rev-parse --short HEAD)
|
||||
|
||||
# Linux AMD64
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
|
||||
-trimpath \
|
||||
-ldflags="-s -w -X main.version=$VERSION -X main.commit=$COMMIT" \
|
||||
-ldflags="-s -w -X main.version=$VERSION" \
|
||||
-o prism-linux-amd64 .
|
||||
|
||||
# Linux ARM64
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build \
|
||||
-trimpath \
|
||||
-ldflags="-s -w -X main.version=$VERSION -X main.commit=$COMMIT" \
|
||||
-ldflags="-s -w -X main.version=$VERSION" \
|
||||
-o prism-linux-arm64 .
|
||||
|
||||
- name: Create GitHub Release
|
||||
|
|
@ -72,7 +71,6 @@ jobs:
|
|||
push: true
|
||||
build-args: |
|
||||
VERSION=${{ steps.version.outputs.tag }}
|
||||
COMMIT=${{ github.sha }}
|
||||
tags: |
|
||||
ghcr.io/lone-cloud/prism:${{ steps.version.outputs.tag }}
|
||||
ghcr.io/lone-cloud/prism:latest
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
FROM golang:1.25-alpine3.23 AS builder
|
||||
|
||||
ARG VERSION=dev
|
||||
ARG COMMIT=unknown
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
|
|
@ -14,7 +13,7 @@ COPY . .
|
|||
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build \
|
||||
-trimpath \
|
||||
-ldflags="-w -s -X main.version=${VERSION} -X main.commit=${COMMIT}" \
|
||||
-ldflags="-w -s -X main.version=${VERSION}" \
|
||||
-o prism .
|
||||
|
||||
FROM debian:trixie-slim
|
||||
|
|
|
|||
3
Makefile
3
Makefile
|
|
@ -2,14 +2,13 @@
|
|||
|
||||
BINARY_NAME=prism
|
||||
VERSION?=$(shell cat VERSION 2>/dev/null || echo "dev")
|
||||
COMMIT?=$(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
||||
GOBIN?=$(shell command -v go >/dev/null 2>&1 && go env GOPATH || echo "${HOME}/go")/bin
|
||||
export PATH := $(GOBIN):$(PATH)
|
||||
|
||||
all: fix build
|
||||
|
||||
build:
|
||||
go build -ldflags="-s -w -X main.version=$(VERSION) -X main.commit=$(COMMIT)" -o $(BINARY_NAME) .
|
||||
go build -ldflags="-s -w -X main.version=$(VERSION)" -o $(BINARY_NAME) .
|
||||
|
||||
start: build
|
||||
./$(BINARY_NAME)
|
||||
|
|
|
|||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
0.2.1
|
||||
0.2.2
|
||||
3
main.go
3
main.go
|
|
@ -21,7 +21,6 @@ var publicAssets embed.FS
|
|||
|
||||
var (
|
||||
version = "dev"
|
||||
commit = "unknown"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
@ -30,7 +29,7 @@ func init() {
|
|||
|
||||
func main() {
|
||||
if len(os.Args) > 1 && os.Args[1] == "version" {
|
||||
fmt.Printf("Prism %s (%s)\n", version, commit)
|
||||
fmt.Printf("Prism %s\n", version)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
/* Variables */
|
||||
:root {
|
||||
--bg-primary: #f5f5f5;
|
||||
--bg-secondary: #fdfdfd;
|
||||
|
|
@ -35,7 +36,7 @@
|
|||
--spinner-track: #4a4a4a;
|
||||
}
|
||||
|
||||
/* CSS Reset */
|
||||
/* Reset */
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
|
|
@ -78,9 +79,11 @@ h6 {
|
|||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
/* Styles */
|
||||
/* Base */
|
||||
body {
|
||||
font-family: system-ui;
|
||||
line-height: 1.5;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
max-width: 50rem;
|
||||
margin: 1rem auto;
|
||||
padding: 0 1.25rem 1.25rem;
|
||||
|
|
@ -88,6 +91,22 @@ body {
|
|||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--accent);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* Components */
|
||||
|
||||
/* Cards */
|
||||
.card {
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 0.5rem;
|
||||
|
|
@ -128,19 +147,7 @@ details[open] > .card-header::before {
|
|||
padding: 0 1.25rem 1.25rem 1.25rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--accent);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* Tooltips */
|
||||
.channel-badge:has(.tooltip),
|
||||
.integration-status:has(.tooltip) {
|
||||
cursor: help;
|
||||
|
|
@ -183,6 +190,7 @@ a:hover {
|
|||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Theme Toggle */
|
||||
.theme-toggle {
|
||||
position: fixed;
|
||||
bottom: 1.5rem;
|
||||
|
|
@ -201,6 +209,7 @@ a:hover {
|
|||
opacity: 1;
|
||||
}
|
||||
|
||||
/* App List */
|
||||
.app-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
|
|
@ -300,6 +309,7 @@ a:hover {
|
|||
filter: brightness(0.85);
|
||||
}
|
||||
|
||||
/* Loading Spinner */
|
||||
.loading {
|
||||
color: var(--text-secondary);
|
||||
display: flex;
|
||||
|
|
@ -322,6 +332,7 @@ a:hover {
|
|||
}
|
||||
}
|
||||
|
||||
/* Integrations */
|
||||
.integration-card {
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 0.5rem;
|
||||
|
|
@ -405,6 +416,7 @@ details[open] > .integration-header::before {
|
|||
color: var(--text-on-color);
|
||||
}
|
||||
|
||||
/* Forms */
|
||||
.auth-form {
|
||||
margin-top: 1rem;
|
||||
max-width: 400px;
|
||||
|
|
@ -433,6 +445,7 @@ details[open] > .integration-header::before {
|
|||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn-primary,
|
||||
.btn-secondary,
|
||||
.btn-danger {
|
||||
|
|
@ -500,6 +513,7 @@ details[open] > .integration-header::before {
|
|||
color: var(--text-on-color);
|
||||
}
|
||||
|
||||
/* QR Code */
|
||||
.qr-container {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<title>Prism Admin</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#8159b8">
|
||||
<meta name="theme-color" content="#60c5ff">
|
||||
<link rel="icon" type="image/webp" href="/favicon.webp">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<link rel="stylesheet" href="/index.css?v={{.Version}}">
|
||||
|
|
|
|||
|
|
@ -1,220 +1,169 @@
|
|||
document.addEventListener('submit', async (e) => {
|
||||
if (e.target.id === 'telegram-auth-form') {
|
||||
e.preventDefault();
|
||||
const form = e.target;
|
||||
const status = document.getElementById('telegram-auth-status');
|
||||
const btn = form.querySelector('button[type="submit"]');
|
||||
async function handleAuthForm(form, endpoint, statusId, getPayload) {
|
||||
const status = document.getElementById(statusId);
|
||||
const btn = form.querySelector('button[type="submit"]');
|
||||
const showError = (msg) => {
|
||||
status.textContent = `Error: ${msg}`;
|
||||
status.className = 'auth-status error';
|
||||
btn.disabled = false;
|
||||
};
|
||||
|
||||
btn.disabled = true;
|
||||
btn.disabled = true;
|
||||
|
||||
try {
|
||||
const formData = new FormData(form);
|
||||
const response = await fetch('/api/telegram/auth', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
bot_token: formData.get('bot_token'),
|
||||
chat_id: formData.get('chat_id'),
|
||||
}),
|
||||
});
|
||||
try {
|
||||
const formData = new FormData(form);
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(getPayload(formData, form)),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
location.reload();
|
||||
} else {
|
||||
const error = await response.json();
|
||||
status.textContent = `Error: ${error.error || 'Failed to save'}`;
|
||||
status.className = 'auth-status error';
|
||||
btn.disabled = false;
|
||||
}
|
||||
} catch (err) {
|
||||
status.textContent = `Error: ${err.message}`;
|
||||
status.className = 'auth-status error';
|
||||
btn.disabled = false;
|
||||
if (response.ok) {
|
||||
location.reload();
|
||||
} else {
|
||||
const error = await response.json();
|
||||
showError(error.error || 'Failed to save');
|
||||
}
|
||||
} catch (err) {
|
||||
showError(err.message);
|
||||
}
|
||||
}
|
||||
|
||||
if (e.target.id === 'telegram-chatid-form') {
|
||||
e.preventDefault();
|
||||
const form = e.target;
|
||||
const status = document.getElementById('telegram-chatid-status');
|
||||
const btn = form.querySelector('button[type="submit"]');
|
||||
// biome-ignore lint/correctness/noUnusedVariables: called from HTML onsubmit
|
||||
async function submitTelegramAuth(e) {
|
||||
e.preventDefault();
|
||||
await handleAuthForm(
|
||||
e.target,
|
||||
'/api/telegram/auth',
|
||||
'telegram-auth-status',
|
||||
(fd) => ({
|
||||
bot_token: fd.get('bot_token'),
|
||||
chat_id: fd.get('chat_id'),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
btn.disabled = true;
|
||||
// biome-ignore lint/correctness/noUnusedVariables: called from HTML onsubmit
|
||||
async function submitTelegramChatId(e) {
|
||||
e.preventDefault();
|
||||
await handleAuthForm(
|
||||
e.target,
|
||||
'/api/telegram/auth',
|
||||
'telegram-chatid-status',
|
||||
(fd, form) => ({
|
||||
bot_token: form.dataset.botToken,
|
||||
chat_id: fd.get('chat_id'),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const formData = new FormData(form);
|
||||
const botToken = form.dataset.botToken;
|
||||
const response = await fetch('/api/telegram/auth', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
bot_token: botToken,
|
||||
chat_id: formData.get('chat_id'),
|
||||
}),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
location.reload();
|
||||
} else {
|
||||
const error = await response.json();
|
||||
status.textContent = `Error: ${error.error || 'Failed to save'}`;
|
||||
status.className = 'auth-status error';
|
||||
btn.disabled = false;
|
||||
}
|
||||
} catch (err) {
|
||||
status.textContent = `Error: ${err.message}`;
|
||||
status.className = 'auth-status error';
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (e.target.id === 'proton-auth-form') {
|
||||
e.preventDefault();
|
||||
const form = e.target;
|
||||
const status = document.getElementById('proton-auth-status');
|
||||
const btn = form.querySelector('button[type="submit"]');
|
||||
|
||||
btn.disabled = true;
|
||||
|
||||
try {
|
||||
const formData = new FormData(form);
|
||||
const response = await fetch('/api/proton/auth', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: formData.get('email'),
|
||||
password: formData.get('password'),
|
||||
totp: formData.get('totp'),
|
||||
}),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
location.reload();
|
||||
} else {
|
||||
const error = await response.json();
|
||||
status.textContent = `Error: ${error.error || 'Authentication failed'}`;
|
||||
status.className = 'auth-status error';
|
||||
btn.disabled = false;
|
||||
}
|
||||
} catch (err) {
|
||||
status.textContent = `Error: ${err.message}`;
|
||||
status.className = 'auth-status error';
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
// biome-ignore lint/correctness/noUnusedVariables: called from HTML onsubmit
|
||||
async function submitProtonAuth(e) {
|
||||
e.preventDefault();
|
||||
await handleAuthForm(
|
||||
e.target,
|
||||
'/api/proton/auth',
|
||||
'proton-auth-status',
|
||||
(fd) => ({
|
||||
email: fd.get('email'),
|
||||
password: fd.get('password'),
|
||||
totp: fd.get('totp'),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
let signalLinkingPoll = null;
|
||||
|
||||
document.addEventListener('click', async (e) => {
|
||||
if (e.target.id === 'signal-link-btn') {
|
||||
const btn = e.target;
|
||||
const qrContainer = document.getElementById('signal-qr-container');
|
||||
const qrCode = document.getElementById('signal-qr-code');
|
||||
// biome-ignore lint/correctness/noUnusedVariables: called from HTML onclick
|
||||
async function linkSignal(btn) {
|
||||
const qrContainer = document.getElementById('signal-qr-container');
|
||||
const qrCode = document.getElementById('signal-qr-code');
|
||||
const showQrError = (msg) => {
|
||||
qrContainer.innerHTML = `<p class="channel-not-configured">Error: ${msg}</p>`;
|
||||
qrContainer.style.display = 'block';
|
||||
btn.style.display = 'inline-block';
|
||||
};
|
||||
|
||||
btn.style.display = 'none';
|
||||
qrContainer.style.display = 'none';
|
||||
btn.style.display = 'none';
|
||||
qrContainer.style.display = 'none';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/signal/link', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ device_name: 'Prism' }),
|
||||
});
|
||||
try {
|
||||
const response = await fetch('/api/signal/link', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ device_name: 'Prism' }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
qrContainer.innerHTML = `<p class="channel-not-configured">Error: ${error.error || 'Failed to generate link'}</p>`;
|
||||
qrContainer.style.display = 'block';
|
||||
btn.style.display = 'inline-block';
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=400x400&data=${encodeURIComponent(data.qr_code)}`;
|
||||
qrCode.src = qrUrl;
|
||||
qrContainer.style.display = 'block';
|
||||
|
||||
signalLinkingPoll = setInterval(async () => {
|
||||
try {
|
||||
const statusResp = await fetch('/api/signal/status');
|
||||
const statusData = await statusResp.json();
|
||||
|
||||
if (statusData.linked) {
|
||||
clearInterval(signalLinkingPoll);
|
||||
qrContainer.innerHTML =
|
||||
'<p class="auth-status success">Linked! Refreshing...</p>';
|
||||
setTimeout(() => location.reload(), 1000);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Status check failed:', err);
|
||||
}
|
||||
}, 2000);
|
||||
} catch (err) {
|
||||
qrContainer.innerHTML = `<p class="channel-not-configured">Error: ${err.message}</p>`;
|
||||
qrContainer.style.display = 'block';
|
||||
btn.style.display = 'inline-block';
|
||||
}
|
||||
}
|
||||
|
||||
if (e.target.classList.contains('reload-btn')) {
|
||||
location.reload();
|
||||
}
|
||||
|
||||
if (e.target.classList.contains('delete-telegram-btn')) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!confirm('Unlink Telegram integration?')) {
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
showQrError(error.error || 'Failed to generate link');
|
||||
return;
|
||||
}
|
||||
|
||||
const btn = e.target;
|
||||
btn.disabled = true;
|
||||
const data = await response.json();
|
||||
const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=400x400&data=${encodeURIComponent(data.qr_code)}`;
|
||||
qrCode.src = qrUrl;
|
||||
qrContainer.style.display = 'block';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/telegram/auth', {
|
||||
method: 'DELETE',
|
||||
});
|
||||
signalLinkingPoll = setInterval(async () => {
|
||||
try {
|
||||
const statusResp = await fetch('/api/signal/status');
|
||||
const statusData = await statusResp.json();
|
||||
|
||||
if (response.ok) {
|
||||
location.reload();
|
||||
} else {
|
||||
const error = await response.json();
|
||||
alert(`Error: ${error.error || 'Failed to unlink integration'}`);
|
||||
btn.disabled = false;
|
||||
if (statusData.linked) {
|
||||
clearInterval(signalLinkingPoll);
|
||||
qrContainer.innerHTML =
|
||||
'<p class="auth-status success">Linked! Refreshing...</p>';
|
||||
setTimeout(() => location.reload(), 1000);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Status check failed:', err);
|
||||
}
|
||||
} catch (err) {
|
||||
alert(`Error: ${err.message}`);
|
||||
}, 2000);
|
||||
} catch (err) {
|
||||
showQrError(err.message);
|
||||
}
|
||||
}
|
||||
|
||||
// biome-ignore lint/correctness/noUnusedVariables: called from HTML onclick
|
||||
async function deleteTelegram(btn) {
|
||||
if (!confirm('Unlink Telegram integration?')) return;
|
||||
|
||||
btn.disabled = true;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/telegram/auth', { method: 'DELETE' });
|
||||
|
||||
if (response.ok) {
|
||||
location.reload();
|
||||
} else {
|
||||
const error = await response.json();
|
||||
alert(`Error: ${error.error || 'Failed to unlink integration'}`);
|
||||
btn.disabled = false;
|
||||
}
|
||||
} catch (err) {
|
||||
alert(`Error: ${err.message}`);
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (e.target.classList.contains('delete-proton-btn')) {
|
||||
if (!confirm('Unlink Proton Mail integration?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/proton/auth', {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
location.reload();
|
||||
} else {
|
||||
const error = await response.json();
|
||||
alert(`Error: ${error.error || 'Failed to unlink integration'}`);
|
||||
}
|
||||
} catch (err) {
|
||||
alert(`Error: ${err.message}`);
|
||||
// biome-ignore lint/correctness/noUnusedVariables: called from HTML onclick
|
||||
async function deleteProton(btn) {
|
||||
if (!confirm('Unlink Proton Mail integration?')) return;
|
||||
|
||||
btn.disabled = true;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/proton/auth', { method: 'DELETE' });
|
||||
|
||||
if (response.ok) {
|
||||
location.reload();
|
||||
} else {
|
||||
const error = await response.json();
|
||||
alert(`Error: ${error.error || 'Failed to unlink integration'}`);
|
||||
btn.disabled = false;
|
||||
}
|
||||
} catch (err) {
|
||||
alert(`Error: ${err.message}`);
|
||||
btn.disabled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#1a1a1a",
|
||||
"theme_color": "#8159b8",
|
||||
"theme_color": "#60c5ff",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/favicon.webp",
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
const themes = ['system', 'light', 'dark'];
|
||||
let currentIndex = 0;
|
||||
|
||||
const savedTheme = localStorage.getItem('theme') || 'system';
|
||||
currentIndex = themes.indexOf(savedTheme);
|
||||
const themes = ['system', 'light', 'dark'];
|
||||
let currentIndex = themes.indexOf(savedTheme);
|
||||
|
||||
if (currentIndex === -1) currentIndex = 0;
|
||||
|
||||
function applyTheme(theme) {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
{{if .Connected}}
|
||||
<button class="btn-danger delete-proton-btn">Unlink</button>
|
||||
<button class="btn-danger" onclick="deleteProton(this)">Unlink</button>
|
||||
{{else}}
|
||||
<p><strong>Link Proton Account:</strong></p>
|
||||
<form id="proton-auth-form" class="auth-form">
|
||||
<form class="auth-form" onsubmit="submitProtonAuth(event)">
|
||||
<div class="form-group">
|
||||
<label for="proton-email">Email:</label>
|
||||
<input type="email" id="proton-email" name="email" placeholder="your-email@proton.me" required>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
{{if .Error}}
|
||||
<p class="channel-not-configured">{{.Error}}</p>
|
||||
{{else}}
|
||||
<button id="signal-link-btn" class="btn-primary">Link</button>
|
||||
<button class="btn-primary" onclick="linkSignal(this)">Link</button>
|
||||
<div id="signal-qr-container" class="qr-container" style="display:none;">
|
||||
<p><strong>Scan this QR code with Signal:</strong></p>
|
||||
<ol class="link-instructions">
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<li>Message <a href="https://t.me/userinfobot" target="_blank">@userinfobot</a> to get your Chat ID</li>
|
||||
<li>Enter both below:</li>
|
||||
</ol>
|
||||
<form id="telegram-auth-form" class="auth-form">
|
||||
<form class="auth-form" onsubmit="submitTelegramAuth(event)">
|
||||
<div class="form-group">
|
||||
<label for="telegram-bot-token">Bot Token:</label>
|
||||
<input type="text" id="telegram-bot-token" name="bot_token" placeholder="123456789:ABCdefGHIjklMNOpqrsTUVwxyz" required>
|
||||
|
|
@ -21,11 +21,11 @@
|
|||
</form>
|
||||
{{else if .Error}}
|
||||
<p>Error: {{.Error}}</p>
|
||||
<button class="btn-secondary reload-btn">Retry</button>
|
||||
<button class="btn-secondary" onclick="location.reload()">Retry</button>
|
||||
{{else if .NeedsChatID}}
|
||||
<p><strong>Complete Setup:</strong></p>
|
||||
<p>Bot is configured, but Chat ID is missing.</p>
|
||||
<form id="telegram-chatid-form" class="auth-form" data-bot-token="{{.BotToken}}">
|
||||
<form class="auth-form" data-bot-token="{{.BotToken}}" onsubmit="submitTelegramChatId(event)">
|
||||
<div class="form-group">
|
||||
<label for="telegram-chat-id-only">Chat ID:</label>
|
||||
<input type="text" id="telegram-chat-id-only" name="chat_id" placeholder="Get from @userinfobot" required>
|
||||
|
|
@ -34,6 +34,6 @@
|
|||
<div id="telegram-chatid-status" class="auth-status"></div>
|
||||
</form>
|
||||
{{else}}
|
||||
<button class="btn-danger delete-telegram-btn">Unlink</button>
|
||||
<button class="btn-danger" onclick="deleteTelegram(this)">Unlink</button>
|
||||
{{end}}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"prism/service/util"
|
||||
)
|
||||
|
||||
func (s *Server) handleIndex(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
if err := s.indexTmpl.Execute(w, map[string]string{"Version": s.version}); err != nil {
|
||||
util.LogAndError(w, s.logger, "Internal Server Error", http.StatusInternalServerError, err)
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,22 @@ import (
|
|||
"prism/service/util"
|
||||
)
|
||||
|
||||
type AppListItem struct {
|
||||
AppName string
|
||||
Channel string
|
||||
ChannelBadge string
|
||||
ChannelConfigured bool
|
||||
Tooltip string
|
||||
Hostname string
|
||||
ChannelOptions []SelectOption
|
||||
}
|
||||
|
||||
type SelectOption struct {
|
||||
Value string
|
||||
Label string
|
||||
Selected bool
|
||||
}
|
||||
|
||||
func (s *Server) handleFragmentApps(w http.ResponseWriter, r *http.Request) {
|
||||
mappings, err := s.store.GetAllMappings()
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -101,7 +101,11 @@ func (s *Server) setupRoutes() {
|
|||
|
||||
r.Get("/health", s.handleHealthCheck)
|
||||
r.Head("/health", s.handleHealthCheck)
|
||||
r.Get("/", s.handleIndex)
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
if err := s.indexTmpl.Execute(w, map[string]string{"Version": s.version}); err != nil {
|
||||
util.LogAndError(w, s.logger, "Internal Server Error", http.StatusInternalServerError, err)
|
||||
}
|
||||
})
|
||||
r.Get("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/favicon.webp", http.StatusMovedPermanently)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
package server
|
||||
|
||||
import "html/template"
|
||||
|
||||
type AppListItem struct {
|
||||
AppName string
|
||||
Channel string
|
||||
ChannelBadge string
|
||||
ChannelConfigured bool
|
||||
Tooltip string
|
||||
Hostname string
|
||||
ChannelOptions []SelectOption
|
||||
}
|
||||
|
||||
type SelectOption struct {
|
||||
Value string
|
||||
Label string
|
||||
Selected bool
|
||||
}
|
||||
|
||||
type IntegrationData struct {
|
||||
Name string
|
||||
StatusClass string
|
||||
StatusText string
|
||||
StatusTooltip string
|
||||
Content template.HTML
|
||||
Open bool
|
||||
PollAttrs string
|
||||
}
|
||||
|
|
@ -77,17 +77,8 @@ func isLocalIP(addr string) bool {
|
|||
}
|
||||
|
||||
func GetClientIP(r *http.Request) string {
|
||||
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
|
||||
parts := strings.Split(xff, ",")
|
||||
return strings.TrimSpace(parts[0])
|
||||
}
|
||||
|
||||
if xri := r.Header.Get("X-Real-IP"); xri != "" {
|
||||
return xri
|
||||
}
|
||||
|
||||
host, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
host, _, _ := net.SplitHostPort(r.RemoteAddr)
|
||||
if host == "" {
|
||||
return r.RemoteAddr
|
||||
}
|
||||
return host
|
||||
|
|
|
|||
|
|
@ -7,12 +7,8 @@ import (
|
|||
)
|
||||
|
||||
func LogAndError(w http.ResponseWriter, logger *slog.Logger, message string, code int, err error, attrs ...any) {
|
||||
if err != nil {
|
||||
logAttrs := append([]any{"error", err}, attrs...)
|
||||
logger.Error(message, logAttrs...)
|
||||
} else {
|
||||
logger.Error(message, attrs...)
|
||||
}
|
||||
logAttrs := append([]any{"error", err}, attrs...)
|
||||
logger.Error(message, logAttrs...)
|
||||
http.Error(w, message, code)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue