Merge branch 'main' of github.com:help-14/magma

This commit is contained in:
NhanPT 2023-03-04 19:42:49 +07:00
commit 1d37fdd9df
11 changed files with 304 additions and 216 deletions

View file

@ -1,130 +1,57 @@
package main
import (
"html/template"
"io/ioutil"
"errors"
"fmt"
"log"
"net/http"
"net"
"os"
"path"
"path/filepath"
"strings"
"time"
"github.com/fsnotify/fsnotify"
docker "github.com/help-14/magma/addons/docker"
healthcheckserver "github.com/help-14/magma/addons/health-check-server"
"github.com/help-14/magma/modules"
)
var pwd string
var themeDir string
var clientAddress string
var appConfig modules.Config
var websiteData = struct {
Config modules.WebsiteConfig
Language modules.Language
Contents []modules.GroupData
}{}
var webTemplate *template.Template
func main() {
prepare()
loadData()
go watchChanges()
commonfs := http.FileServer(http.Dir(filepath.Join(pwd, "data")))
http.Handle("/common/", http.StripPrefix("/common/", commonfs))
languagefs := http.FileServer(http.Dir(filepath.Join(pwd, "languages")))
http.Handle("/languages/", http.StripPrefix("/languages/", languagefs))
th := themeHandler{}
http.Handle("/theme/", th)
http.HandleFunc("/weather", serveWeather)
http.HandleFunc("/", serveTemplate)
mux := http.NewServeMux()
modules.SetupLanguage(mux)
modules.SetupTemplate(mux)
modules.SetupWeather(mux)
//loadAddons()
log.Println("Listening on http://localhost:7001 ...")
err := http.ListenAndServe(":7001", nil)
if err != nil {
log.Fatal(err)
server := http.Server{
Addr: fmt.Sprintf(":%d", 7001),
Handler: mux,
}
log.Println("Listening on http://localhost:7001 ...")
if err := server.ListenAndServe(); err != nil {
if !errors.Is(err, http.ErrServerClosed) {
fmt.Printf("error running http server: %s\n", err)
}
}
// err := http.ListenAndServe(":7001", nil)
// if err != nil {
// log.Fatal(err)
// }
}
func prepare() {
pwd, _ = os.Getwd()
dataPath := filepath.Join(pwd, "data")
dataPath := filepath.Join(modules.CurrentPath(), "data")
os.MkdirAll(dataPath, os.ModePerm)
iconPath := filepath.Join(dataPath, "icon")
os.RemoveAll(iconPath)
os.MkdirAll(iconPath, os.ModePerm)
modules.CopyDir(filepath.Join(pwd, "common"), dataPath, false)
}
func RemoveIndex(s []modules.BookmarkData, index int) {
copy(s[index:], s[index+1:])
s[len(s)-1] = modules.BookmarkData{"", "", "", false}
s = s[:len(s)-1]
}
func pruneData() {
// Remove local ressources access
for group := 0; group < len(websiteData.Contents); group++ {
for col := 0; col < len(websiteData.Contents[group].Columns); col++ {
bookmarks := websiteData.Contents[group].Columns[col].Bookmarks
for bookmark := 0; bookmark < len(websiteData.Contents[group].Columns[col].Bookmarks); bookmark++ {
bookmarkData := websiteData.Contents[group].Columns[col].Bookmarks[bookmark]
if bookmarkData.IsLocal {
RemoveIndex(bookmarks, bookmark)
bookmark--
}
}
websiteData.Contents[group].Columns[col].Bookmarks = bookmarks
}
}
loadTemplate()
}
func loadData() {
appConfig = modules.LoadConfig()
websiteData.Config = appConfig.Website
websiteData.Language = modules.LoadLanguage(appConfig.Website.Language)
websiteData.Contents = modules.LoadContent().Data
// Download icon to local and remove local ressources access
for group := 0; group < len(websiteData.Contents); group++ {
for col := 0; col < len(websiteData.Contents[group].Columns); col++ {
for bookmark := 0; bookmark < len(websiteData.Contents[group].Columns[col].Bookmarks); bookmark++ {
bookmarkData := websiteData.Contents[group].Columns[col].Bookmarks[bookmark]
if bookmarkData.IsImage() || bookmarkData.IsSVG() {
iconPath := bookmarkData.Icon
fileName := path.Base(iconPath)
if modules.DownloadFile(iconPath, filepath.Join(pwd, "data", "icon", fileName)) {
websiteData.Contents[group].Columns[col].Bookmarks[bookmark].Icon = "/common/icon/" + fileName
}
}
}
}
}
loadTemplate()
}
func loadTemplate() {
themeDir = filepath.Join(pwd, "themes", appConfig.Website.Theme)
tmpl, _ := template.ParseFiles(filepath.Join(themeDir, "index.html"))
webTemplate = tmpl
modules.CopyDir(filepath.Join(modules.CurrentPath(), "common"), dataPath, false)
}
func loadAddons() {
for i := 0; i < len(appConfig.Addons); i++ {
switch addonName := appConfig.Addons[i]; addonName {
for i := 0; i < len(modules.AppConfig.Addons); i++ {
switch addonName := modules.AppConfig.Addons[i]; addonName {
case "docker":
docker.Setup()
case "health-check-server":
@ -132,89 +59,3 @@ func loadAddons() {
}
}
}
func watchChanges() {
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
defer watcher.Close()
done := make(chan bool)
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
log.Println("Modified file:", event.Name)
loadData()
case err, ok := <-watcher.Errors:
if !ok {
return
}
log.Println("error:", err)
}
}
}()
watcher.Add(filepath.Join(pwd, "data", "data.yaml"))
watcher.Add(filepath.Join(pwd, "data", "config.yaml"))
watcher.Add(filepath.Join(pwd, "themes", appConfig.Website.Theme, "index.html"))
<-done
}
func ClientIsLocal(r *http.Request) bool {
IPAddress := net.ParseIP(r.Header.Get("X-Real-Ip"))
if IPAddress == nil {
IPAddress = net.ParseIP(r.Header.Get("X-Forwarded-For"))
}
if IPAddress == nil {
IPAddress = net.ParseIP(strings.Split(r.RemoteAddr, ":")[0])
}
return IPAddress.IsPrivate()
}
func serveTemplate(w http.ResponseWriter, r *http.Request) {
if ! ClientIsLocal(r) {
pruneData()
} else {
loadData()
}
webTemplate.Execute(w, websiteData)
}
type themeHandler struct {
format string
}
func (th themeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, strings.Replace(r.URL.Path, "/theme", themeDir, 1))
}
var weatherTimeOut int64
var weatherCache []byte
func serveWeather(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
if appConfig.OpenWeatherMap.ApiKey == "demo" {
w.Write([]byte("{\"coord\":{\"lon\":105.8085,\"lat\":21.0427},\"weather\":[{\"id\":803,\"main\":\"Clouds\",\"description\":\"broken clouds\",\"icon\":\"04n\"}],\"base\":\"stations\",\"main\":{\"temp\":301.14,\"feels_like\":305.69,\"temp_min\":301.14,\"temp_max\":301.14,\"pressure\":1004,\"humidity\":83},\"visibility\":10000,\"wind\":{\"speed\":6.17,\"deg\":120},\"clouds\":{\"all\":75},\"dt\":1650981392,\"sys\":{\"type\":1,\"id\":9308,\"country\":\"VN\",\"sunrise\":1650925786,\"sunset\":1650971952},\"timezone\":25200,\"id\":1581130,\"name\":\"Hanoi\",\"cod\":200}"))
} else {
if time.Now().UnixMilli() >= weatherTimeOut {
resp, err := http.Get("https://api.openweathermap.org/data/2.5/weather?lat=" + appConfig.OpenWeatherMap.Latitude + "&lon=" + appConfig.OpenWeatherMap.Longitude + "&limit=1&appid=" + appConfig.OpenWeatherMap.ApiKey)
if err != nil {
log.Fatalln(err)
return
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatalln(err)
return
}
weatherCache = body
weatherTimeOut = time.Now().UnixMilli() + 1800000
}
w.Write(weatherCache)
}
}

View file

@ -2,7 +2,6 @@ package modules
import (
"fmt"
"io/ioutil"
"path/filepath"
"gopkg.in/yaml.v2"
@ -29,7 +28,9 @@ type OpenWeatherMapConfig struct {
Latitude string `yaml:"lat"`
}
func LoadConfig() Config {
var AppConfig Config
func LoadConfig() {
defaultConfig := Config{
Website: WebsiteConfig{
Title: "Magma Dashboard",
@ -41,20 +42,21 @@ func LoadConfig() Config {
},
Addons: []string{},
}
AppConfig = defaultConfig
yamlFile, err := ioutil.ReadFile(filepath.Join("data", "config.yaml"))
yamlFile, err := ReadFile(filepath.Join("data", "config.yaml"))
if err != nil {
fmt.Printf("Error reading YAML file: %s\n", err)
return defaultConfig
return
}
var yamlConfig Config
err = yaml.Unmarshal(yamlFile, &yamlConfig)
if err != nil {
fmt.Printf("Error parsing YAML file: %s\n", err)
return defaultConfig
return
}
fmt.Println("Loaded config:", yamlConfig)
return yamlConfig
AppConfig = yamlConfig
}

View file

@ -3,7 +3,6 @@ package modules
import (
"fmt"
"index/suffixarray"
"io/ioutil"
"path/filepath"
"regexp"
"strings"
@ -28,16 +27,17 @@ type ColumnData struct {
}
type BookmarkData struct {
Name string `yaml:"name"`
Url string `yaml:"url"`
Icon string `yaml:"icon"`
IsLocal bool `yaml:"isLocal"`
Name string `yaml:"name"`
Url string `yaml:"url"`
UrlLocal string `yaml:"urlLocal"`
Icon string `yaml:"icon"`
IsLocal bool `yaml:"isLocal"`
}
func LoadContent() ContentData {
emptyData := ContentData{}
yamlFile, err := ioutil.ReadFile(filepath.Join("data", "data.yaml"))
yamlFile, err := ReadFile(filepath.Join("data", "data.yaml"))
if err != nil {
fmt.Printf("Error reading YAML file: %s\n", err)
return emptyData

View file

@ -7,6 +7,15 @@ import (
"os"
)
var pwd string
func CurrentPath() string {
if len(pwd) <= 0 {
pwd, _ = os.Getwd()
}
return pwd
}
func Exists(path string) bool {
if _, err := os.Stat(path); !os.IsNotExist(err) {
return true
@ -14,12 +23,20 @@ func Exists(path string) bool {
return false
}
func ReadFile(path string) ([]byte, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
return io.ReadAll(file)
}
func CopyFile(source string, dest string) (err error) {
sourcefile, err := os.Open(source)
if err != nil {
return err
}
defer sourcefile.Close()
destfile, err := os.Create(dest)
@ -33,7 +50,7 @@ func CopyFile(source string, dest string) (err error) {
if err == nil {
sourceinfo, err := os.Stat(source)
if err != nil {
err = os.Chmod(dest, sourceinfo.Mode())
_ = os.Chmod(dest, sourceinfo.Mode())
}
}

View file

@ -2,12 +2,17 @@ package modules
import (
"fmt"
"io/ioutil"
"net/http"
"path/filepath"
"gopkg.in/yaml.v2"
)
func SetupLanguage(mux *http.ServeMux) {
languagefs := http.FileServer(http.Dir(filepath.Join(CurrentPath(), "languages")))
mux.Handle("/languages/", http.StripPrefix("/languages/", languagefs))
}
type Language struct {
Greeting LanguageGreeting `yaml:"greeting"`
Weather LanguageWeather `yaml:"weather"`
@ -36,7 +41,7 @@ type LanguageWeather struct {
}
func LoadLanguage(language string) Language {
yamlFile, err := ioutil.ReadFile(filepath.Join("languages", language+".yaml"))
yamlFile, err := ReadFile(filepath.Join("languages", language+".yaml"))
if err != nil {
fmt.Printf("Error reading YAML file: %s\n", err)
return LoadLanguage("en")

18
src/modules/network.go Normal file
View file

@ -0,0 +1,18 @@
package modules
import (
"net"
"net/http"
"strings"
)
func ClientIsLocal(r *http.Request) bool {
IPAddress := net.ParseIP(r.Header.Get("X-Real-Ip"))
if IPAddress == nil {
IPAddress = net.ParseIP(r.Header.Get("X-Forwarded-For"))
}
if IPAddress == nil {
IPAddress = net.ParseIP(strings.Split(r.RemoteAddr, ":")[0])
}
return IPAddress.IsPrivate()
}

159
src/modules/template.go Normal file
View file

@ -0,0 +1,159 @@
package modules
import (
"fmt"
"log"
"net/http"
"path"
"path/filepath"
"strings"
"text/template"
"github.com/fsnotify/fsnotify"
)
func SetupTemplate(mux *http.ServeMux) {
loadData()
go watchChanges()
commonfs := http.FileServer(http.Dir(filepath.Join(CurrentPath(), "data")))
mux.Handle("/common/", http.StripPrefix("/common/", commonfs))
th := themeHandler{}
mux.Handle("/theme/", th)
mux.HandleFunc("/", serveTemplate)
}
var websiteData = struct {
Config WebsiteConfig
Language Language
Contents []GroupData
IsLocal bool
}{}
var privateContent []GroupData
var publicContent []GroupData
var webTemplate *template.Template
func loadData() {
LoadConfig()
websiteData.Config = AppConfig.Website
websiteData.Language = LoadLanguage(AppConfig.Website.Language)
websiteData.Contents = LoadContent().Data
loadTemplate()
// Download icon to local and remove local ressources access
for groupIndex := 0; groupIndex < len(websiteData.Contents); groupIndex++ {
group := websiteData.Contents[groupIndex]
groupDataPublic := []ColumnData{}
groupDataPrivate := []ColumnData{}
for colIndex := 0; colIndex < len(group.Columns); colIndex++ {
column := group.Columns[colIndex]
columnDataPublic := []BookmarkData{}
columnDataPrivate := []BookmarkData{}
for bookmarkIndex := 0; bookmarkIndex < len(column.Bookmarks); bookmarkIndex++ {
bookmarkData := column.Bookmarks[bookmarkIndex]
iconPath := bookmarkData.Icon
//download icon
if bookmarkData.IsImage() || bookmarkData.IsSVG() {
fileName := path.Base(iconPath)
if DownloadFile(iconPath, filepath.Join(CurrentPath(), "data", "icon", fileName)) {
iconPath = "/common/icon/" + fileName
}
}
//add to private array
url := bookmarkData.Url
if len(bookmarkData.UrlLocal) > 0 {
url = bookmarkData.UrlLocal
}
if len(url) > 0 {
columnDataPrivate = append(columnDataPrivate, BookmarkData{bookmarkData.Name, url, bookmarkData.UrlLocal, iconPath, true})
}
//add to public array
if !bookmarkData.IsLocal && len(bookmarkData.Url) > 0 {
columnDataPublic = append(columnDataPublic, BookmarkData{bookmarkData.Name, bookmarkData.Url, bookmarkData.UrlLocal, iconPath, false})
}
}
if len(columnDataPublic) > 0 {
groupDataPublic = append(groupDataPublic, ColumnData{column.Title, columnDataPublic, column.Icon})
}
if len(columnDataPrivate) > 0 {
groupDataPrivate = append(groupDataPrivate, ColumnData{column.Title, columnDataPrivate, column.Icon})
}
}
if len(groupDataPublic) > 0 {
publicContent = append(publicContent, GroupData{group.Title, groupDataPublic, group.Icon})
}
if len(groupDataPrivate) > 0 {
privateContent = append(privateContent, GroupData{group.Title, groupDataPrivate, group.Icon})
}
}
}
var themeDir string
type themeHandler struct {
format string
}
func (th themeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
newPath := strings.Replace(r.URL.Path, "/theme", themeDir, 1)
fmt.Println(newPath)
http.ServeFile(w, r, newPath)
}
func loadTemplate() {
themeDir = filepath.Join(CurrentPath(), "themes", AppConfig.Website.Theme)
tmpl, _ := template.ParseFiles(filepath.Join(themeDir, "index.html"))
webTemplate = tmpl
}
func serveTemplate(w http.ResponseWriter, r *http.Request) {
websiteData.IsLocal = ClientIsLocal(r)
if websiteData.IsLocal {
websiteData.Contents = privateContent
} else {
websiteData.Contents = publicContent
}
webTemplate.Execute(w, websiteData)
}
func watchChanges() {
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
return
}
defer watcher.Close()
done := make(chan bool)
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
log.Println("Modified file:", event.Name)
loadData()
case err, ok := <-watcher.Errors:
if !ok {
return
}
log.Println("error:", err)
}
}
}()
watcher.Add(filepath.Join(CurrentPath(), "data", "data.yaml"))
watcher.Add(filepath.Join(CurrentPath(), "data", "config.yaml"))
watcher.Add(filepath.Join(CurrentPath(), "themes", AppConfig.Website.Theme, "index.html"))
<-done
}

46
src/modules/weather.go Normal file
View file

@ -0,0 +1,46 @@
package modules
import (
"io"
"log"
"net/http"
"time"
)
func SetupWeather(mux *http.ServeMux) {
mux.HandleFunc("/weather", serveWeather)
}
func serveWeather(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
data := GetWeather(AppConfig.OpenWeatherMap.ApiKey, AppConfig.OpenWeatherMap.Latitude, AppConfig.OpenWeatherMap.Longitude)
if data != nil {
w.Write(data)
}
}
var weatherTimeOut int64
var weatherCache []byte
var demoData = []byte("{\"coord\":{\"lon\":105.8085,\"lat\":21.0427},\"weather\":[{\"id\":803,\"main\":\"Clouds\",\"description\":\"broken clouds\",\"icon\":\"04n\"}],\"base\":\"stations\",\"main\":{\"temp\":301.14,\"feels_like\":305.69,\"temp_min\":301.14,\"temp_max\":301.14,\"pressure\":1004,\"humidity\":83},\"visibility\":10000,\"wind\":{\"speed\":6.17,\"deg\":120},\"clouds\":{\"all\":75},\"dt\":1650981392,\"sys\":{\"type\":1,\"id\":9308,\"country\":\"VN\",\"sunrise\":1650925786,\"sunset\":1650971952},\"timezone\":25200,\"id\":1581130,\"name\":\"Hanoi\",\"cod\":200}")
func GetWeather(apiKey string, latitude string, longitude string) []byte {
if apiKey == "demo" {
return demoData
} else {
if time.Now().UnixMilli() >= weatherTimeOut {
resp, err := http.Get("https://api.openweathermap.org/data/2.5/weather?lat=" + latitude + "&lon=" + longitude + "&limit=1&appid=" + apiKey)
if err != nil {
log.Fatalln(err)
return nil
}
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatalln(err)
return nil
}
weatherCache = body
weatherTimeOut = time.Now().UnixMilli() + 1800000
}
return weatherCache
}
}

View file

@ -91,8 +91,8 @@
<script src="/common/js/core.js"></script>
<script>
window.config = {
localization: {{.Config.Localization}},
language: {{.Config.Language}},
localization: '{{.Config.Localization}}',
language: '{{.Config.Language}}',
useMetric: {{.Config.UseMetric}}
};
@ -107,16 +107,16 @@
const greeting = document.querySelector("#greeting");
const hour = new Date().getHours();
if (hour >= 5 && hour < 12) {
greeting.innerText = {{.Language.Greeting.Morning}};
greeting.innerText = '{{.Language.Greeting.Morning}}'
}
else if (hour >= 12 && hour < 17) {
greeting.innerText = {{.Language.Greeting.Afternoon}};
greeting.innerText = '{{.Language.Greeting.Afternoon}}'
}
else if (hour >= 17 && hour < 20) {
greeting.innerText = {{.Language.Greeting.Evening}};
greeting.innerText = '{{.Language.Greeting.Evening}}'
}
else {
greeting.innerText = {{.Language.Greeting.Night}};
greeting.innerText = '{{.Language.Greeting.Night}}'
}
}) ();

View file

@ -90,8 +90,8 @@
<script src="/common/js/core.js"></script>
<script>
window.config = {
localization: {{.Config.Localization}},
language: {{.Config.Language}},
localization: '{{.Config.Localization}}',
language: '{{.Config.Language}}',
useMetric: {{.Config.UseMetric}}
};
@ -109,16 +109,16 @@
const greeting = document.querySelector("#greeting");
const hour = new Date().getHours();
if (hour >= 5 && hour < 12) {
greeting.innerText = {{.Language.Greeting.Morning}};
greeting.innerText = '{{.Language.Greeting.Morning}}'
}
else if (hour >= 12 && hour < 17) {
greeting.innerText = {{.Language.Greeting.Afternoon}};
greeting.innerText = '{{.Language.Greeting.Afternoon}}'
}
else if (hour >= 17 && hour < 20) {
greeting.innerText = {{.Language.Greeting.Evening}};
greeting.innerText = '{{.Language.Greeting.Evening}}'
}
else {
greeting.innerText = {{.Language.Greeting.Night}};
greeting.innerText = '{{.Language.Greeting.Night}}'
}
}) ();

View file

@ -44,24 +44,24 @@
<script src="/common/js/core.js"></script>
<script>
window.config = {
localization: {{.Config.Localization}},
language: {{.Config.Language}},
localization: '{{.Config.Localization}}',
language: '{{.Config.Language}}',
useMetric: {{.Config.UseMetric}}
};
(function setTimer() {
const greeting = document.querySelector("#greeting");
const hour = new Date().getHours();
if (hour >= 5 && hour < 12) {
greeting.innerText = {{.Language.Greeting.Morning}};
greeting.innerText = '{{.Language.Greeting.Morning}}'
}
else if (hour >= 12 && hour < 17) {
greeting.innerText = {{.Language.Greeting.Afternoon}};
greeting.innerText = '{{.Language.Greeting.Afternoon}}'
}
else if (hour >= 17 && hour < 20) {
greeting.innerText = {{.Language.Greeting.Evening}};
greeting.innerText = '{{.Language.Greeting.Evening}}'
}
else {
greeting.innerText = {{.Language.Greeting.Night}};
greeting.innerText = '{{.Language.Greeting.Night}}'
}
}) ();
</script>