added backend
This commit is contained in:
1 parent
9842ccee3d
commit
40bcd02c36
10 files changed
+340
-75
No files matched your search
@@ -0,0 +1 @@
|
|||||||
|
go.sum
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
ENABLE_TLS=false
|
||||||
|
ROOT_DIRECTORY=../
|
||||||
|
INDEX_FILE=index.html
|
||||||
|
SERVER_ADDRESS=0.0.0.0
|
||||||
|
SERVER_PORT=8090
|
||||||
|
SERVICES_FILE=services.json
|
||||||
|
IMG_UPLOAD_PATH=../img
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AddService(w http.ResponseWriter, req *http.Request) {
|
||||||
|
req.ParseMultipartForm(10 << 20)
|
||||||
|
file, handler, err := req.FormFile("img")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("error retrieving the file: ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
fmt.Printf("Uploaded File: %+v\n", handler.Filename)
|
||||||
|
fmt.Printf("File Size: %+v\n", handler.Size)
|
||||||
|
fmt.Printf("MIME Header: %+v\n", handler.Header)
|
||||||
|
|
||||||
|
dst, err := os.Create(os.Getenv("IMG_UPLOAD_PATH") + "/" + handler.Filename)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error creating destination file:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer dst.Close()
|
||||||
|
if _, err := io.Copy(dst, file); err != nil {
|
||||||
|
fmt.Println("Error copying file:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
newService := make(map[string]any)
|
||||||
|
newService["img"] = "img/" + handler.Filename
|
||||||
|
newService["href"] = req.FormValue("href")
|
||||||
|
newService["text"] = req.FormValue("text")
|
||||||
|
newService["alt"] = req.FormValue("text") + "icon"
|
||||||
|
newService["key"] = uuid.New().String()
|
||||||
|
fmt.Println(newService)
|
||||||
|
allservices := ReadServicesFile()
|
||||||
|
var allservicesmap []map[string]any
|
||||||
|
err = json.Unmarshal([]byte(allservices), &allservicesmap)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
exists := false
|
||||||
|
for i := range allservicesmap {
|
||||||
|
if allservicesmap[i]["href"] == newService["href"] {
|
||||||
|
exists = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
allservicesmap = append(allservicesmap, newService)
|
||||||
|
res, err := json.Marshal(allservicesmap)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
os.WriteFile(os.Getenv("SERVICES_FILE"), res, 4755)
|
||||||
|
fmt.Fprintf(w, "%s", res)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprint(w, "{\"status\":\"already exists\"}")
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ReadServicesFile() string {
|
||||||
|
data, e := os.ReadFile(os.Getenv("SERVICES_FILE"))
|
||||||
|
if e != nil {
|
||||||
|
panic(e)
|
||||||
|
}
|
||||||
|
return string(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Services(w http.ResponseWriter, req *http.Request) {
|
||||||
|
switch req.Method {
|
||||||
|
case "GET":
|
||||||
|
fmt.Fprintf(w, "%s", ReadServicesFile())
|
||||||
|
case "POST":
|
||||||
|
data, err := io.ReadAll(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(w, "Error to read body: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(string(data))
|
||||||
|
os.WriteFile(os.Getenv("SERVICES_FILE"), data, 4755)
|
||||||
|
fmt.Fprint(w, string(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SpaHandler(staticPath, indexPath string) http.Handler {
|
||||||
|
fs := http.FileServer(http.Dir(staticPath))
|
||||||
|
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Игнорируем API-запросы
|
||||||
|
if strings.HasPrefix(r.URL.Path, "/api/") {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем существование файла
|
||||||
|
filePath := filepath.Join(staticPath, r.URL.Path)
|
||||||
|
_, err := os.Stat(filePath)
|
||||||
|
|
||||||
|
// Если файл не существует или путь - SPA-маршрут
|
||||||
|
if os.IsNotExist(err) || isSPARoute(r.URL.Path) {
|
||||||
|
r.URL.Path = "/" // Переписываем путь для index.html
|
||||||
|
}
|
||||||
|
|
||||||
|
// Отдаем статический файл
|
||||||
|
fs.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSPARoute(path string) bool {
|
||||||
|
spaRoutes := []string{"/", "/update", "/trx"} // Добавьте свои маршруты
|
||||||
|
return slices.Contains(spaRoutes, path)
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
module dshbrd
|
||||||
|
|
||||||
|
go 1.24.5
|
||||||
|
|
||||||
|
require github.com/joho/godotenv v1.5.1
|
||||||
|
|
||||||
|
require github.com/google/uuid v1.6.0 // indirect
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"dshbrd/api"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
err := godotenv.Load(".env")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("error loading .env file")
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Handle("/", api.SpaHandler(os.Getenv("ROOT_DIRECTORY"), os.Getenv("INDEX_FILE")))
|
||||||
|
http.HandleFunc("/api/services", api.Services)
|
||||||
|
http.HandleFunc("/api/services/add", api.AddService)
|
||||||
|
|
||||||
|
if os.Getenv("ENABLE_TLS") == "true" {
|
||||||
|
http.ListenAndServeTLS(
|
||||||
|
os.Getenv("SERVER_ADDRESS")+":"+os.Getenv("SERVER_PORT"),
|
||||||
|
os.Getenv("CERT_FILE"),
|
||||||
|
os.Getenv("KEY_FILE"),
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
http.ListenAndServe(
|
||||||
|
os.Getenv("SERVER_ADDRESS")+":"+os.Getenv("SERVER_PORT"),
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
[{"alt":"telegram-icon","href":"https://web.telegram.org/k/","img":"img/telegram-icon.jpeg","key":"0","text":"Telegram"},{"alt":"github-icon","href":"https://github.com","img":"img/github-icon.png","key":"1","text":"github.com"},{"alt":"gitea-icon","href":"https://gitea.home.com:3000","img":"img/gitea-icon.png","key":"2","text":"gitea.home.com"},{"alt":"jellyfin-icon","href":"http://jellyfin.home.com:8096","img":"img/jellyfin.png","key":"3","text":"jellyfin.home.com"},{"alt":"immich-icon","href":"http://immich.home.com:2283","img":"img/immich-icon.png","key":"4","text":"immich.home.com"},{"alt":"navidrome-icon","href":"http://navidrome.home.com:4533","img":"img/navidrome-icon.png","key":"4","text":"navidrome.home.com"},{"alt":"portainer-icon","href":"https://portainer.home.com:9443","img":"img/portainer-icon.webp","key":"5","text":"portainer.home.com"},{"alt":"dnsmasq-icon","href":"https://dns.home.com","img":"img/dnsmasq-icon.jpeg","key":"6","text":"dns.home.com"},{"alt":"router-icon","href":"http://router.home.com","img":"img/router-icon.svg","key":"7","text":"router.home.com"}]
|
||||||
+70
-2
@@ -6,12 +6,12 @@
|
|||||||
<link rel="icon" href="img/logo.ico" />
|
<link rel="icon" href="img/logo.ico" />
|
||||||
<link rel="stylesheet" href="css/index.css" />
|
<link rel="stylesheet" href="css/index.css" />
|
||||||
<link rel="stylesheet" href="css/bootstrap.min.css" />
|
<link rel="stylesheet" href="css/bootstrap.min.css" />
|
||||||
|
<script src="js/bootstrap.min.js" defer></script>
|
||||||
<script src="js/icons.js" defer></script>
|
<script src="js/icons.js" defer></script>
|
||||||
<!-- <script src="js/mails.js" defer></script> -->
|
<!-- <script src="js/mails.js" defer></script> -->
|
||||||
<script src="js/time.js" defer></script>
|
<script src="js/time.js" defer></script>
|
||||||
<script src="js/search.js" defer></script>
|
<script src="js/search.js" defer></script>
|
||||||
<script src="js/theme.js" defer></script>
|
<script src="js/theme.js" defer></script>
|
||||||
<script src="js/bootstrap.min.js" defer></script>
|
|
||||||
<title>Dashboard</title>
|
<title>Dashboard</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -43,7 +43,15 @@
|
|||||||
<h1 class="navbar-item" id="time"></h1>
|
<h1 class="navbar-item" id="time"></h1>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<form class="d-flex">
|
<form class="d-flex gap-3">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#addcard"
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</button>
|
||||||
<input
|
<input
|
||||||
class="form-control"
|
class="form-control"
|
||||||
type="text"
|
type="text"
|
||||||
@@ -59,5 +67,65 @@
|
|||||||
class="d-flex gap-3 m-3 flex-wrap align-items-center justify-content-center"
|
class="d-flex gap-3 m-3 flex-wrap align-items-center justify-content-center"
|
||||||
id="icons"
|
id="icons"
|
||||||
></div>
|
></div>
|
||||||
|
<div class="modal fade" tabindex="-1" id="addcard">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Add Card</h5>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
data-bs-dismiss="modal"
|
||||||
|
aria-label="Close"
|
||||||
|
class="btn-close"
|
||||||
|
></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="form-floating mb-3">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="servicenameinput"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="name"
|
||||||
|
/>
|
||||||
|
<label for="servicenameinput">Name</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-floating mb-3">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="servicelinkinput"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="example.com"
|
||||||
|
/>
|
||||||
|
<label for="servicelinkinput">Url</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label for="serviceimageinput">Image</label>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
id="serviceimageinput"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="example.com"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
data-bs-dismiss="modal"
|
||||||
|
class="btn btn-secondary"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
id="savecardbutton"
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
+82
-73
@@ -1,77 +1,86 @@
|
|||||||
const iconsArea = document.querySelector("#icons");
|
const iconsArea = document.querySelector("#icons");
|
||||||
|
const servicenameinput = document.querySelector("#servicenameinput");
|
||||||
|
const servicelinkinput = document.querySelector("#servicelinkinput");
|
||||||
|
const serviceimageinput = document.querySelector("#serviceimageinput");
|
||||||
|
const savecardbutton = document.querySelector("#savecardbutton");
|
||||||
|
const addcardmodalel = document.querySelector("#addcard");
|
||||||
|
const addcardmodal = new bootstrap.Modal(addcardmodalel);
|
||||||
|
|
||||||
const icons = [
|
var icons = [];
|
||||||
{
|
|
||||||
key: 0,
|
|
||||||
href: "https://web.telegram.org/k/",
|
|
||||||
img: "img/telegram-icon.jpeg",
|
|
||||||
alt: "telegram-icon",
|
|
||||||
text: "Telegram",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 1,
|
|
||||||
href: "https://github.com",
|
|
||||||
img: "img/github-icon.png",
|
|
||||||
alt: "github-icon",
|
|
||||||
text: "github.com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 0,
|
|
||||||
href: "https://gitea.home.com:3000",
|
|
||||||
img: "img/gitea-icon.png",
|
|
||||||
alt: "gitea-icon",
|
|
||||||
text: "gitea.home.com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 1,
|
|
||||||
href: "http://jellyfin.home.com:8096",
|
|
||||||
img: "img/jellyfin.png",
|
|
||||||
alt: "jellyfin-icon",
|
|
||||||
text: "jellyfin.home.com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 2,
|
|
||||||
href: "http://immich.home.com:2283",
|
|
||||||
img: "img/immich-icon.png",
|
|
||||||
alt: "immich-icon",
|
|
||||||
text: "immich.home.com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 3,
|
|
||||||
href: "http://navidrome.home.com:4533",
|
|
||||||
img: "img/navidrome-icon.png",
|
|
||||||
alt: "navidrome-icon",
|
|
||||||
text: "navidrome.home.com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 4,
|
|
||||||
href: "https://portainer.home.com:9443",
|
|
||||||
img: "img/portainer-icon.webp",
|
|
||||||
alt: "portainer-icon",
|
|
||||||
text: "portainer.home.com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 5,
|
|
||||||
href: "https://dns.home.com",
|
|
||||||
img: "img/dnsmasq-icon.jpeg",
|
|
||||||
alt: "dnsmasq-icon",
|
|
||||||
text: "dns.home.com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 7,
|
|
||||||
href: "http://router.home.com",
|
|
||||||
img: "img/router-icon.svg",
|
|
||||||
alt: "router-icon",
|
|
||||||
text: "router.home.com",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
icons.map((icon) => {
|
const renderIcons = () => {
|
||||||
iconsArea.innerHTML += `<div class="card" style="width: 16rem;">
|
iconsArea.innerHTML = "";
|
||||||
<img src="${icon.img}" alt="${icon.alt}" class="card-img-top">
|
icons.map((icon) => {
|
||||||
<div class="card-body">
|
iconsArea.innerHTML += `<div class="card" style="width: 16rem;">
|
||||||
<h5 class="card-title" style="text-align: center;">${icon.text}</h5>
|
<img src="${icon.img}" alt="${icon.alt}" class="card-img-top">
|
||||||
<a href="${icon.href}" class="btn btn-primary" style="width: 100%;">Visit</a>
|
<div class="card-body">
|
||||||
</div>
|
<h5 class="card-title" style="text-align: center;">${icon.text}</h5>
|
||||||
</div>`;
|
<div class="d-flex flex-column gap-2">
|
||||||
|
<a href="${icon.href}" class="btn btn-primary" style="width: 100%;">Visit</a>
|
||||||
|
<button class="btn btn-danger delcard" style="width: 100%" id="${icon.key}">Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getIcons = () => {
|
||||||
|
fetch(window.location.origin + "/api/services")
|
||||||
|
.then((data) => data.json())
|
||||||
|
.then((jsondata) => {
|
||||||
|
icons = jsondata;
|
||||||
|
renderIcons();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
getIcons();
|
||||||
|
|
||||||
|
savecardbutton.addEventListener("click", () => {
|
||||||
|
if (confirm("Add card?")) {
|
||||||
|
const file = serviceimageinput.files[0];
|
||||||
|
if (!file) {
|
||||||
|
alert("Please select a file to upload");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formdata = new FormData();
|
||||||
|
formdata.append("img", file);
|
||||||
|
formdata.append("text", servicenameinput.value);
|
||||||
|
formdata.append("href", servicelinkinput.value);
|
||||||
|
fetch(window.location.origin + "/api/services/add", {
|
||||||
|
method: "POST",
|
||||||
|
body: formdata,
|
||||||
|
})
|
||||||
|
.then((data) => data.json())
|
||||||
|
.then((jsondata) => {
|
||||||
|
if (jsondata.status === "already exists") {
|
||||||
|
alert("Service already exists");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
icons = jsondata;
|
||||||
|
renderIcons();
|
||||||
|
addcardmodal.hide();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
alert(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
iconsArea.addEventListener("click", (e) => {
|
||||||
|
if (e.target.classList.contains("delcard")) {
|
||||||
|
if (confirm("Delete card?")) {
|
||||||
|
icons.splice(icons.indexOf(icons.find((i) => i.key === e.target.id)), 1);
|
||||||
|
fetch(window.location.origin + "/api/services", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(icons),
|
||||||
|
})
|
||||||
|
.then((data) => data.json())
|
||||||
|
.then((jsondata) => {
|
||||||
|
icons = jsondata;
|
||||||
|
renderIcons();
|
||||||
|
})
|
||||||
|
.catch((error) => alert(error));
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
Reference in new issue
Block a user