Add Websocket authentication #216
Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
parent
f569c3adb6
commit
7342d5194a
|
@ -15,7 +15,7 @@ const Notify = {
|
||||||
},
|
},
|
||||||
logout: function (message) {
|
logout: function (message) {
|
||||||
Event.publish("notify.error", {msg: message});
|
Event.publish("notify.error", {msg: message});
|
||||||
Event.publish("session.logout");
|
Event.publish("session.logout", {msg: message});
|
||||||
},
|
},
|
||||||
ajaxStart: function() {
|
ajaxStart: function() {
|
||||||
Event.publish("ajax.start");
|
Event.publish("ajax.start");
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import Api from "./api";
|
import Api from "./api";
|
||||||
import Event from "pubsub-js";
|
import Event from "pubsub-js";
|
||||||
import User from "../model/user";
|
import User from "../model/user";
|
||||||
|
import Socket from "./websocket";
|
||||||
|
|
||||||
export default class Session {
|
export default class Session {
|
||||||
/**
|
/**
|
||||||
|
@ -24,7 +25,15 @@ export default class Session {
|
||||||
this.auth = true;
|
this.auth = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Event.subscribe("session.logout", this.onLogout.bind(this));
|
Event.subscribe("session.logout", () => {
|
||||||
|
this.onLogout()
|
||||||
|
});
|
||||||
|
|
||||||
|
Event.subscribe("websocket.connected", () => {
|
||||||
|
this.sendClientInfo()
|
||||||
|
});
|
||||||
|
|
||||||
|
this.sendClientInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
useSessionStorage() {
|
useSessionStorage() {
|
||||||
|
@ -118,6 +127,21 @@ export default class Session {
|
||||||
this.storage.removeItem("user");
|
this.storage.removeItem("user");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sendClientInfo() {
|
||||||
|
const clientInfo = {
|
||||||
|
"session": this.getToken(),
|
||||||
|
"js": window.clientConfig.jsHash,
|
||||||
|
"css": window.clientConfig.cssHash,
|
||||||
|
"version": window.clientConfig.version,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
Socket.send(JSON.stringify(clientInfo));
|
||||||
|
} catch(e) {
|
||||||
|
console.log("can't send client info, websocket not connected (yet)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
login(email, password) {
|
login(email, password) {
|
||||||
this.deleteToken();
|
this.deleteToken();
|
||||||
|
|
||||||
|
@ -125,11 +149,13 @@ export default class Session {
|
||||||
(result) => {
|
(result) => {
|
||||||
this.setToken(result.data.token);
|
this.setToken(result.data.token);
|
||||||
this.setUser(new User(result.data.user));
|
this.setUser(new User(result.data.user));
|
||||||
|
this.sendClientInfo();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onLogout() {
|
onLogout() {
|
||||||
|
console.log("ON LOGOUT");
|
||||||
this.deleteToken();
|
this.deleteToken();
|
||||||
window.location = "/";
|
window.location = "/";
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,15 @@
|
||||||
import Sockette from "sockette";
|
import Sockette from "sockette";
|
||||||
import Event from "pubsub-js";
|
import Event from "pubsub-js";
|
||||||
import randomString from "crypto-random-string";
|
|
||||||
|
|
||||||
export const token = randomString({length: 16});
|
|
||||||
const host = window.location.host;
|
const host = window.location.host;
|
||||||
const prot = ("https:" === document.location.protocol ? "wss://" : "ws://");
|
const prot = ("https:" === document.location.protocol ? "wss://" : "ws://");
|
||||||
const url = prot + host + "/api/v1/ws";
|
const url = prot + host + "/api/v1/ws";
|
||||||
const clientInfo = {
|
|
||||||
"token": token,
|
|
||||||
"hash": window.clientConfig.jsHash,
|
|
||||||
"version": window.clientConfig.version,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Socket = new Sockette(url, {
|
const Socket = new Sockette(url, {
|
||||||
timeout: 5e3,
|
timeout: 5e3,
|
||||||
onopen: e => {
|
onopen: e => {
|
||||||
console.log("websocket: connected", e);
|
console.log("websocket: connected", e);
|
||||||
Socket.send(JSON.stringify(clientInfo));
|
Event.publish("websocket.connected", e);
|
||||||
},
|
},
|
||||||
onmessage: e => {
|
onmessage: e => {
|
||||||
const m = JSON.parse(e.data);
|
const m = JSON.parse(e.data);
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
</v-toolbar>
|
</v-toolbar>
|
||||||
<v-navigation-drawer
|
<v-navigation-drawer
|
||||||
v-model="drawer"
|
v-model="drawer"
|
||||||
:mini-variant="mini"
|
:mini-variant="mini || !auth"
|
||||||
class="p-navigation-sidebar navigation"
|
class="p-navigation-sidebar navigation"
|
||||||
width="270"
|
width="270"
|
||||||
fixed dark app
|
fixed dark app
|
||||||
|
@ -35,14 +35,14 @@
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-toolbar>
|
</v-toolbar>
|
||||||
|
|
||||||
<v-list class="pt-3">
|
<v-list class="pt-3" v-if="auth">
|
||||||
<v-list-tile v-if="mini" @click.stop="mini = !mini" class="p-navigation-expand">
|
<v-list-tile v-if="mini" @click.stop="mini = !mini" class="p-navigation-expand">
|
||||||
<v-list-tile-action>
|
<v-list-tile-action>
|
||||||
<v-icon>chevron_right</v-icon>
|
<v-icon>chevron_right</v-icon>
|
||||||
</v-list-tile-action>
|
</v-list-tile-action>
|
||||||
</v-list-tile>
|
</v-list-tile>
|
||||||
|
|
||||||
<v-list-tile v-if="mini && auth" to="/photos" @click="" class="p-navigation-photos">
|
<v-list-tile v-if="mini" to="/photos" @click="" class="p-navigation-photos">
|
||||||
<v-list-tile-action>
|
<v-list-tile-action>
|
||||||
<v-icon>photo</v-icon>
|
<v-icon>photo</v-icon>
|
||||||
</v-list-tile-action>
|
</v-list-tile-action>
|
||||||
|
@ -54,7 +54,7 @@
|
||||||
</v-list-tile-content>
|
</v-list-tile-content>
|
||||||
</v-list-tile>
|
</v-list-tile>
|
||||||
|
|
||||||
<v-list-group v-if="!mini && auth" prepend-icon="photo" no-action>
|
<v-list-group v-if="!mini" prepend-icon="photo" no-action>
|
||||||
<v-list-tile slot="activator" to="/photos" @click.stop="" class="p-navigation-photos">
|
<v-list-tile slot="activator" to="/photos" @click.stop="" class="p-navigation-photos">
|
||||||
<v-list-tile-content>
|
<v-list-tile-content>
|
||||||
<v-list-tile-title>
|
<v-list-tile-title>
|
||||||
|
@ -89,7 +89,7 @@
|
||||||
</v-list-tile>
|
</v-list-tile>
|
||||||
</v-list-group>
|
</v-list-group>
|
||||||
|
|
||||||
<v-list-tile v-if="mini && auth" to="/archive" @click="" class="p-navigation-archive">
|
<v-list-tile v-if="mini" to="/archive" @click="" class="p-navigation-archive">
|
||||||
<v-list-tile-action>
|
<v-list-tile-action>
|
||||||
<v-icon>archive</v-icon>
|
<v-icon>archive</v-icon>
|
||||||
</v-list-tile-action>
|
</v-list-tile-action>
|
||||||
|
@ -101,7 +101,7 @@
|
||||||
</v-list-tile-content>
|
</v-list-tile-content>
|
||||||
</v-list-tile>
|
</v-list-tile>
|
||||||
|
|
||||||
<v-list-tile v-if="mini && auth" to="/albums" @click="">
|
<v-list-tile v-if="mini" to="/albums" @click="">
|
||||||
<v-list-tile-action>
|
<v-list-tile-action>
|
||||||
<v-icon>folder</v-icon>
|
<v-icon>folder</v-icon>
|
||||||
</v-list-tile-action>
|
</v-list-tile-action>
|
||||||
|
@ -113,7 +113,7 @@
|
||||||
</v-list-tile-content>
|
</v-list-tile-content>
|
||||||
</v-list-tile>
|
</v-list-tile>
|
||||||
|
|
||||||
<v-list-group v-if="!mini && auth" prepend-icon="folder" no-action>
|
<v-list-group v-if="!mini" prepend-icon="folder" no-action>
|
||||||
<v-list-tile slot="activator" to="/albums" @click.stop="">
|
<v-list-tile slot="activator" to="/albums" @click.stop="">
|
||||||
<v-list-tile-content>
|
<v-list-tile-content>
|
||||||
<v-list-tile-title>
|
<v-list-tile-title>
|
||||||
|
@ -133,7 +133,7 @@
|
||||||
</v-list-tile>
|
</v-list-tile>
|
||||||
</v-list-group>
|
</v-list-group>
|
||||||
|
|
||||||
<v-list-tile to="/favorites" @click="" class="p-navigation-favorites" v-if="auth">
|
<v-list-tile to="/favorites" @click="" class="p-navigation-favorites">
|
||||||
<v-list-tile-action>
|
<v-list-tile-action>
|
||||||
<v-icon>favorite</v-icon>
|
<v-icon>favorite</v-icon>
|
||||||
</v-list-tile-action>
|
</v-list-tile-action>
|
||||||
|
@ -146,7 +146,7 @@
|
||||||
</v-list-tile-content>
|
</v-list-tile-content>
|
||||||
</v-list-tile>
|
</v-list-tile>
|
||||||
|
|
||||||
<v-list-tile to="/labels" @click="" class="p-navigation-labels" v-if="auth">
|
<v-list-tile to="/labels" @click="" class="p-navigation-labels">
|
||||||
<v-list-tile-action>
|
<v-list-tile-action>
|
||||||
<v-icon>label</v-icon>
|
<v-icon>label</v-icon>
|
||||||
</v-list-tile-action>
|
</v-list-tile-action>
|
||||||
|
@ -159,7 +159,7 @@
|
||||||
</v-list-tile-content>
|
</v-list-tile-content>
|
||||||
</v-list-tile>
|
</v-list-tile>
|
||||||
|
|
||||||
<v-list-tile :to="{ name: 'places' }" @click="" class="p-navigation-places" v-if="auth">
|
<v-list-tile :to="{ name: 'places' }" @click="" class="p-navigation-places">
|
||||||
<v-list-tile-action>
|
<v-list-tile-action>
|
||||||
<v-icon>place</v-icon>
|
<v-icon>place</v-icon>
|
||||||
</v-list-tile-action>
|
</v-list-tile-action>
|
||||||
|
@ -172,7 +172,7 @@
|
||||||
</v-list-tile-content>
|
</v-list-tile-content>
|
||||||
</v-list-tile>
|
</v-list-tile>
|
||||||
|
|
||||||
<v-list-tile to="/discover" @click="" class="p-navigation-discover" v-if="config.experimental && auth">
|
<v-list-tile to="/discover" @click="" class="p-navigation-discover" v-if="config.experimental">
|
||||||
<v-list-tile-action>
|
<v-list-tile-action>
|
||||||
<v-icon>color_lens</v-icon>
|
<v-icon>color_lens</v-icon>
|
||||||
</v-list-tile-action>
|
</v-list-tile-action>
|
||||||
|
@ -184,7 +184,7 @@
|
||||||
</v-list-tile-content>
|
</v-list-tile-content>
|
||||||
</v-list-tile>
|
</v-list-tile>
|
||||||
|
|
||||||
<!-- v-list-tile to="/events" @click="" class="p-navigation-events" v-if="auth">
|
<!-- v-list-tile to="/events" @click="" class="p-navigation-events">
|
||||||
<v-list-tile-action>
|
<v-list-tile-action>
|
||||||
<v-icon>date_range</v-icon>
|
<v-icon>date_range</v-icon>
|
||||||
</v-list-tile-action>
|
</v-list-tile-action>
|
||||||
|
@ -194,7 +194,7 @@
|
||||||
</v-list-tile-content>
|
</v-list-tile-content>
|
||||||
</v-list-tile -->
|
</v-list-tile -->
|
||||||
|
|
||||||
<!-- v-list-tile to="/people" @click="" class="p-navigation-people" v-if="auth">
|
<!-- v-list-tile to="/people" @click="" class="p-navigation-people">
|
||||||
<v-list-tile-action>
|
<v-list-tile-action>
|
||||||
<v-icon>people</v-icon>
|
<v-icon>people</v-icon>
|
||||||
</v-list-tile-action>
|
</v-list-tile-action>
|
||||||
|
@ -204,7 +204,7 @@
|
||||||
</v-list-tile-content>
|
</v-list-tile-content>
|
||||||
</v-list-tile -->
|
</v-list-tile -->
|
||||||
|
|
||||||
<v-list-tile to="/library" @click="" class="p-navigation-library" v-if="auth">
|
<v-list-tile to="/library" @click="" class="p-navigation-library">
|
||||||
<v-list-tile-action>
|
<v-list-tile-action>
|
||||||
<v-icon>camera_roll</v-icon>
|
<v-icon>camera_roll</v-icon>
|
||||||
</v-list-tile-action>
|
</v-list-tile-action>
|
||||||
|
@ -216,7 +216,7 @@
|
||||||
</v-list-tile-content>
|
</v-list-tile-content>
|
||||||
</v-list-tile>
|
</v-list-tile>
|
||||||
|
|
||||||
<v-list-tile to="/settings" @click="" class="p-navigation-settings" v-if="auth">
|
<v-list-tile to="/settings" @click="" class="p-navigation-settings">
|
||||||
<v-list-tile-action>
|
<v-list-tile-action>
|
||||||
<v-icon>settings</v-icon>
|
<v-icon>settings</v-icon>
|
||||||
</v-list-tile-action>
|
</v-list-tile-action>
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/photoprism/photoprism/internal/config"
|
"github.com/photoprism/photoprism/internal/config"
|
||||||
|
"github.com/photoprism/photoprism/internal/event"
|
||||||
"github.com/photoprism/photoprism/internal/form"
|
"github.com/photoprism/photoprism/internal/form"
|
||||||
"github.com/photoprism/photoprism/internal/session"
|
"github.com/photoprism/photoprism/internal/session"
|
||||||
"github.com/photoprism/photoprism/pkg/txt"
|
"github.com/photoprism/photoprism/pkg/txt"
|
||||||
|
@ -33,6 +34,8 @@ func CreateSession(router *gin.RouterGroup, conf *config.Config) {
|
||||||
|
|
||||||
s := gin.H{"token": token, "user": user}
|
s := gin.H{"token": token, "user": user}
|
||||||
|
|
||||||
|
event.Publish("config.updated", event.Data(conf.ClientConfig()))
|
||||||
|
|
||||||
c.JSON(http.StatusOK, s)
|
c.JSON(http.StatusOK, s)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/photoprism/photoprism/internal/config"
|
"github.com/photoprism/photoprism/internal/config"
|
||||||
"github.com/photoprism/photoprism/internal/event"
|
"github.com/photoprism/photoprism/internal/event"
|
||||||
|
"github.com/photoprism/photoprism/internal/session"
|
||||||
|
"github.com/photoprism/photoprism/pkg/rnd"
|
||||||
)
|
)
|
||||||
|
|
||||||
var wsConnection = websocket.Upgrader{
|
var wsConnection = websocket.Upgrader{
|
||||||
|
@ -17,7 +21,19 @@ var wsConnection = websocket.Upgrader{
|
||||||
}
|
}
|
||||||
var wsTimeout = 60 * time.Second
|
var wsTimeout = 60 * time.Second
|
||||||
|
|
||||||
func wsReader(ws *websocket.Conn) {
|
type clientInfo struct {
|
||||||
|
SessionToken string `json:"session"`
|
||||||
|
JsHash string `json:"js"`
|
||||||
|
CssHash string `json:"css"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var wsAuth = struct {
|
||||||
|
authenticated map[string]bool
|
||||||
|
mutex sync.RWMutex
|
||||||
|
}{authenticated: make(map[string]bool)}
|
||||||
|
|
||||||
|
func wsReader(ws *websocket.Conn, connId string) {
|
||||||
defer ws.Close()
|
defer ws.Close()
|
||||||
|
|
||||||
ws.SetReadLimit(512)
|
ws.SetReadLimit(512)
|
||||||
|
@ -26,14 +42,30 @@ func wsReader(ws *websocket.Conn) {
|
||||||
|
|
||||||
for {
|
for {
|
||||||
_, m, err := ws.ReadMessage()
|
_, m, err := ws.ReadMessage()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("websocket: received %d bytes", len(m))
|
log.Debugf("websocket: received %d bytes", len(m))
|
||||||
|
|
||||||
|
var info clientInfo
|
||||||
|
|
||||||
|
if err := json.Unmarshal(m, &info); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
} else {
|
||||||
|
log.Debugf("websocket: %+v", info)
|
||||||
|
|
||||||
|
if session.Exists(info.SessionToken) {
|
||||||
|
wsAuth.mutex.Lock()
|
||||||
|
wsAuth.authenticated[connId] = true
|
||||||
|
wsAuth.mutex.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func wsWriter(ws *websocket.Conn) {
|
func wsWriter(ws *websocket.Conn, connId string) {
|
||||||
pingTicker := time.NewTicker(10 * time.Second)
|
pingTicker := time.NewTicker(10 * time.Second)
|
||||||
s := event.Subscribe("log.*", "notify.*", "index.*", "upload.*", "import.*", "config.*", "count.*")
|
s := event.Subscribe("log.*", "notify.*", "index.*", "upload.*", "import.*", "config.*", "count.*")
|
||||||
|
|
||||||
|
@ -41,6 +73,10 @@ func wsWriter(ws *websocket.Conn) {
|
||||||
pingTicker.Stop()
|
pingTicker.Stop()
|
||||||
event.Unsubscribe(s)
|
event.Unsubscribe(s)
|
||||||
ws.Close()
|
ws.Close()
|
||||||
|
|
||||||
|
wsAuth.mutex.Lock()
|
||||||
|
wsAuth.authenticated[connId] = false
|
||||||
|
wsAuth.mutex.Unlock()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
@ -51,11 +87,17 @@ func wsWriter(ws *websocket.Conn) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case msg := <-s.Receiver:
|
case msg := <-s.Receiver:
|
||||||
ws.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
wsAuth.mutex.RLock()
|
||||||
|
auth := wsAuth.authenticated[connId]
|
||||||
|
wsAuth.mutex.RUnlock()
|
||||||
|
|
||||||
if err := ws.WriteJSON(gin.H{"event": msg.Name, "data": msg.Fields}); err != nil {
|
if auth {
|
||||||
log.Debug(err)
|
ws.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
||||||
return
|
|
||||||
|
if err := ws.WriteJSON(gin.H{"event": msg.Name, "data": msg.Fields}); err != nil {
|
||||||
|
log.Debug(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,6 +105,16 @@ func wsWriter(ws *websocket.Conn) {
|
||||||
|
|
||||||
// GET /api/v1/ws
|
// GET /api/v1/ws
|
||||||
func Websocket(router *gin.RouterGroup, conf *config.Config) {
|
func Websocket(router *gin.RouterGroup, conf *config.Config) {
|
||||||
|
if router == nil {
|
||||||
|
log.Error("websocket: router is nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if conf == nil {
|
||||||
|
log.Error("websocket: conf is nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
router.GET("/ws", func(c *gin.Context) {
|
router.GET("/ws", func(c *gin.Context) {
|
||||||
w := c.Writer
|
w := c.Writer
|
||||||
r := c.Request
|
r := c.Request
|
||||||
|
@ -75,10 +127,18 @@ func Websocket(router *gin.RouterGroup, conf *config.Config) {
|
||||||
|
|
||||||
defer ws.Close()
|
defer ws.Close()
|
||||||
|
|
||||||
|
connId := rnd.UUID()
|
||||||
|
|
||||||
|
if conf.Public() {
|
||||||
|
wsAuth.mutex.Lock()
|
||||||
|
wsAuth.authenticated[connId] = true
|
||||||
|
wsAuth.mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
log.Debug("websocket: connected")
|
log.Debug("websocket: connected")
|
||||||
|
|
||||||
go wsWriter(ws)
|
go wsWriter(ws, connId)
|
||||||
|
|
||||||
wsReader(ws)
|
wsReader(ws, connId)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,84 @@ import (
|
||||||
// HTTP client / Web UI config values
|
// HTTP client / Web UI config values
|
||||||
type ClientConfig map[string]interface{}
|
type ClientConfig map[string]interface{}
|
||||||
|
|
||||||
|
// PublicClientConfig returns reduced config values for non-public sites.
|
||||||
|
func (c *Config) PublicClientConfig() ClientConfig {
|
||||||
|
if c.Public() {
|
||||||
|
return c.ClientConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
jsHash := fs.Hash(c.HttpStaticBuildPath() + "/app.js")
|
||||||
|
cssHash := fs.Hash(c.HttpStaticBuildPath() + "/app.css")
|
||||||
|
|
||||||
|
// Feature Flags
|
||||||
|
var flags []string
|
||||||
|
|
||||||
|
if c.Public() {
|
||||||
|
flags = append(flags, "public")
|
||||||
|
}
|
||||||
|
if c.Debug() {
|
||||||
|
flags = append(flags, "debug")
|
||||||
|
}
|
||||||
|
if c.Experimental() {
|
||||||
|
flags = append(flags, "experimental")
|
||||||
|
}
|
||||||
|
if c.ReadOnly() {
|
||||||
|
flags = append(flags, "readonly")
|
||||||
|
}
|
||||||
|
|
||||||
|
var noPos = struct {
|
||||||
|
PhotoUUID string `json:"photo"`
|
||||||
|
LocationID string `json:"location"`
|
||||||
|
TakenAt time.Time `json:"utc"`
|
||||||
|
PhotoLat float64 `json:"lat"`
|
||||||
|
PhotoLng float64 `json:"lng"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
var count = struct {
|
||||||
|
Photos uint `json:"photos"`
|
||||||
|
Favorites uint `json:"favorites"`
|
||||||
|
Private uint `json:"private"`
|
||||||
|
Stories uint `json:"stories"`
|
||||||
|
Labels uint `json:"labels"`
|
||||||
|
Albums uint `json:"albums"`
|
||||||
|
Countries uint `json:"countries"`
|
||||||
|
Places uint `json:"places"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
result := ClientConfig{
|
||||||
|
"flags": strings.Join(flags, " "),
|
||||||
|
"name": c.Name(),
|
||||||
|
"url": c.Url(),
|
||||||
|
"title": c.Title(),
|
||||||
|
"subtitle": c.Subtitle(),
|
||||||
|
"description": c.Description(),
|
||||||
|
"author": c.Author(),
|
||||||
|
"twitter": c.Twitter(),
|
||||||
|
"version": c.Version(),
|
||||||
|
"copyright": c.Copyright(),
|
||||||
|
"debug": c.Debug(),
|
||||||
|
"readonly": c.ReadOnly(),
|
||||||
|
"uploadNSFW": c.UploadNSFW(),
|
||||||
|
"public": c.Public(),
|
||||||
|
"experimental": c.Experimental(),
|
||||||
|
"albums": []string{},
|
||||||
|
"cameras": []string{},
|
||||||
|
"lenses": []string{},
|
||||||
|
"countries": []string{},
|
||||||
|
"thumbnails": Thumbnails,
|
||||||
|
"jsHash": jsHash,
|
||||||
|
"cssHash": cssHash,
|
||||||
|
"settings": c.Settings(),
|
||||||
|
"count": count,
|
||||||
|
"pos": noPos,
|
||||||
|
"years": []int{},
|
||||||
|
"colors": colors.All.List(),
|
||||||
|
"categories": []string{},
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// ClientConfig returns a loaded and set configuration entity.
|
// ClientConfig returns a loaded and set configuration entity.
|
||||||
func (c *Config) ClientConfig() ClientConfig {
|
func (c *Config) ClientConfig() ClientConfig {
|
||||||
db := c.Db()
|
db := c.Db()
|
||||||
|
|
|
@ -72,6 +72,7 @@ func registerRoutes(router *gin.Engine, conf *config.Config) {
|
||||||
|
|
||||||
// Default HTML page (client-side routing implemented via Vue.js)
|
// Default HTML page (client-side routing implemented via Vue.js)
|
||||||
router.NoRoute(func(c *gin.Context) {
|
router.NoRoute(func(c *gin.Context) {
|
||||||
|
// Todo: Use PublicClientConfig()
|
||||||
c.HTML(http.StatusOK, "index.tmpl", gin.H{"clientConfig": conf.ClientConfig()})
|
c.HTML(http.StatusOK, "index.tmpl", gin.H{"clientConfig": conf.ClientConfig()})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue