Browse Source

Add a landing login page and a logout option.

BasicAuth without an explicit landing page or a logout option has
sometimes been confusing to users. This commit adds a static
landing page on / with a login link and a logout option in the admin
that "logs out" BasicAuth session by posting invalid credentials to
the server to obtain a 401.
Kailash Nadh 3 years ago
parent
commit
98ed4fb384

+ 2 - 2
cmd/handlers.go

@@ -51,8 +51,8 @@ func initHTTPHandlers(e *echo.Echo, app *App) {
 
 
 	// Admin JS app views.
 	// Admin JS app views.
 	// /admin/static/* file server is registered in initHTTPServer().
 	// /admin/static/* file server is registered in initHTTPServer().
-	g.GET("/", func(c echo.Context) error {
-		return c.Redirect(http.StatusPermanentRedirect, path.Join(adminRoot, ""))
+	e.GET("/", func(c echo.Context) error {
+		return c.Render(http.StatusOK, "home", publicTpl{Title: "listmonk"})
 	})
 	})
 	g.GET(path.Join(adminRoot, ""), handleAdminPage)
 	g.GET(path.Join(adminRoot, ""), handleAdminPage)
 	g.GET(path.Join(adminRoot, "/*"), handleAdminPage)
 	g.GET(path.Join(adminRoot, "/*"), handleAdminPage)

+ 19 - 1
frontend/src/App.vue

@@ -10,7 +10,9 @@
           </div>
           </div>
         </template>
         </template>
         <template slot="end">
         <template slot="end">
-            <b-navbar-item tag="div"></b-navbar-item>
+            <b-navbar-item tag="div">
+              <a href="#" @click.prevent="doLogout">{{ $t('users.logout') }}</a>
+            </b-navbar-item>
         </template>
         </template>
     </b-navbar>
     </b-navbar>
 
 
@@ -134,6 +136,7 @@
 <script>
 <script>
 import Vue from 'vue';
 import Vue from 'vue';
 import { mapState } from 'vuex';
 import { mapState } from 'vuex';
+import { uris } from './constants';
 
 
 export default Vue.extend({
 export default Vue.extend({
   name: 'App',
   name: 'App',
@@ -164,6 +167,21 @@ export default Vue.extend({
     toggleGroup(group, state) {
     toggleGroup(group, state) {
       this.activeGroup = state ? { [group]: true } : {};
       this.activeGroup = state ? { [group]: true } : {};
     },
     },
+
+    doLogout() {
+      const http = new XMLHttpRequest();
+
+      const u = uris.root.substr(-1) === '/' ? uris.root : `${uris.root}/`;
+      http.open('get', `${u}api/logout`, false, 'logout_non_user', 'logout_non_user');
+      http.onload = () => {
+        document.location.href = uris.root;
+      };
+      http.onerror = () => {
+        document.location.href = uris.root;
+      };
+      http.send();
+    },
+
     reloadApp() {
     reloadApp() {
       this.$api.reloadApp().then(() => {
       this.$api.reloadApp().then(() => {
         this.$utils.toast('Reloading app ...');
         this.$utils.toast('Reloading app ...');

+ 5 - 1
frontend/src/api/index.js

@@ -6,7 +6,7 @@ import store from '../store';
 import { models } from '../constants';
 import { models } from '../constants';
 
 
 const http = axios.create({
 const http = axios.create({
-  baseURL: process.env.VUE_APP_API_URL || '/',
+  baseURL: process.env.VUE_APP_ROOT_URL || '/',
   withCredentials: false,
   withCredentials: false,
   responseType: 'json',
   responseType: 'json',
 
 
@@ -276,3 +276,7 @@ export const getLogs = async () => http.get('/api/logs',
 
 
 export const getLang = async (lang) => http.get(`/api/lang/${lang}`,
 export const getLang = async (lang) => http.get(`/api/lang/${lang}`,
   { loading: models.lang, preserveCase: true });
   { loading: models.lang, preserveCase: true });
+
+export const logout = async () => http.get('/api/logout', {
+  auth: { username: 'wrong', password: 'wrong' },
+});

+ 0 - 4
frontend/src/assets/style.scss

@@ -939,10 +939,6 @@ section.analytics {
     }
     }
   }
   }
 
 
-  .navbar-burger {
-    display: none;
-  }
-
   td .tags {
   td .tags {
     .tag:not(:last-child) {
     .tag:not(:last-child) {
       margin-right: 0;
       margin-right: 0;

+ 3 - 0
frontend/src/constants.js

@@ -13,6 +13,7 @@ export const models = Object.freeze({
 });
 });
 
 
 // Ad-hoc URIs that are used outside of vuex requests.
 // Ad-hoc URIs that are used outside of vuex requests.
+const rootURL = process.env.VUE_APP_ROOT_URL || '/';
 const baseURL = process.env.BASE_URL.replace(/\/$/, '');
 const baseURL = process.env.BASE_URL.replace(/\/$/, '');
 
 
 export const uris = Object.freeze({
 export const uris = Object.freeze({
@@ -20,6 +21,8 @@ export const uris = Object.freeze({
   previewTemplate: '/api/templates/:id/preview',
   previewTemplate: '/api/templates/:id/preview',
   previewRawTemplate: '/api/templates/preview',
   previewRawTemplate: '/api/templates/preview',
   exportSubscribers: '/api/subscribers/export',
   exportSubscribers: '/api/subscribers/export',
+  base: `${baseURL}/static`,
+  root: rootURL,
   static: `${baseURL}/static`,
   static: `${baseURL}/static`,
 });
 });
 
 

+ 3 - 1
i18n/cs-cz.json

@@ -482,5 +482,7 @@
     "templates.newTemplate": "Nová šablona",
     "templates.newTemplate": "Nová šablona",
     "templates.placeholderHelp": "Zástupný symbol {placeholder} by se měl v šabloně objevit právě jednou.",
     "templates.placeholderHelp": "Zástupný symbol {placeholder} by se měl v šabloně objevit právě jednou.",
     "templates.preview": "Náhled",
     "templates.preview": "Náhled",
-    "templates.rawHTML": "Kód HTML"
+    "templates.rawHTML": "Kód HTML",
+    "users.login": "Login",
+    "users.logout": "Logout"
 }
 }

+ 3 - 1
i18n/de.json

@@ -482,5 +482,7 @@
     "templates.newTemplate": "Neue Vorlage",
     "templates.newTemplate": "Neue Vorlage",
     "templates.placeholderHelp": "Der Platzhalter \"{placeholder}\" darf nur einmal im Template vorkommen.",
     "templates.placeholderHelp": "Der Platzhalter \"{placeholder}\" darf nur einmal im Template vorkommen.",
     "templates.preview": "Vorschau",
     "templates.preview": "Vorschau",
-    "templates.rawHTML": "HTML"
+    "templates.rawHTML": "HTML",
+    "users.login": "Login",
+    "users.logout": "Logout"
 }
 }

+ 3 - 1
i18n/en.json

@@ -482,5 +482,7 @@
     "templates.newTemplate": "New template",
     "templates.newTemplate": "New template",
     "templates.placeholderHelp": "The placeholder {placeholder} should appear exactly once in the template.",
     "templates.placeholderHelp": "The placeholder {placeholder} should appear exactly once in the template.",
     "templates.preview": "Preview",
     "templates.preview": "Preview",
-    "templates.rawHTML": "Raw HTML"
+    "templates.rawHTML": "Raw HTML",
+    "users.login": "Login",
+    "users.logout": "Logout"
 }
 }

+ 3 - 1
i18n/es.json

@@ -482,5 +482,7 @@
     "templates.newTemplate": "Nueva plantilla",
     "templates.newTemplate": "Nueva plantilla",
     "templates.placeholderHelp": "El marcador {placeholder} debe aparecer exactamente una vez en la plantilla.",
     "templates.placeholderHelp": "El marcador {placeholder} debe aparecer exactamente una vez en la plantilla.",
     "templates.preview": "Vista pewliminar",
     "templates.preview": "Vista pewliminar",
-    "templates.rawHTML": "HTML crudo"
+    "templates.rawHTML": "HTML crudo",
+    "users.login": "Login",
+    "users.logout": "Logout"
 }
 }

+ 3 - 1
i18n/fr.json

@@ -482,5 +482,7 @@
     "templates.newTemplate": "Nouveau modèle",
     "templates.newTemplate": "Nouveau modèle",
     "templates.placeholderHelp": "L'espace réservé {placeholder} doit apparaître exactement une fois dans le modèle.",
     "templates.placeholderHelp": "L'espace réservé {placeholder} doit apparaître exactement une fois dans le modèle.",
     "templates.preview": "Aperçu",
     "templates.preview": "Aperçu",
-    "templates.rawHTML": "HTML brut"
+    "templates.rawHTML": "HTML brut",
+    "users.login": "Login",
+    "users.logout": "Logout"
 }
 }

+ 3 - 1
i18n/it.json

@@ -482,5 +482,7 @@
     "templates.newTemplate": "Nuovo modello",
     "templates.newTemplate": "Nuovo modello",
     "templates.placeholderHelp": "Il segnaposto {placeholder} deve apparire esattamente una volta nel modello.",
     "templates.placeholderHelp": "Il segnaposto {placeholder} deve apparire esattamente una volta nel modello.",
     "templates.preview": "Anteprima",
     "templates.preview": "Anteprima",
-    "templates.rawHTML": "HTML semplice"
+    "templates.rawHTML": "HTML semplice",
+    "users.login": "Login",
+    "users.logout": "Logout"
 }
 }

+ 3 - 1
i18n/ml.json

@@ -482,5 +482,7 @@
     "templates.newTemplate": "പുതിയ ടെംപ്ലേറ്റ്",
     "templates.newTemplate": "പുതിയ ടെംപ്ലേറ്റ്",
     "templates.placeholderHelp": "{placeholder} എന്ന പ്ലെയ്‌സ്‌ഹോൾഡർ ടെംപ്ലേറ്റിൽ ഒരിക്കലെങ്കിലും വരണം.",
     "templates.placeholderHelp": "{placeholder} എന്ന പ്ലെയ്‌സ്‌ഹോൾഡർ ടെംപ്ലേറ്റിൽ ഒരിക്കലെങ്കിലും വരണം.",
     "templates.preview": "പ്രിവ്യൂ",
     "templates.preview": "പ്രിവ്യൂ",
-    "templates.rawHTML": "എച്. ടീ. എം. എൽ"
+    "templates.rawHTML": "എച്. ടീ. എം. എൽ",
+    "users.login": "Login",
+    "users.logout": "Logout"
 }
 }

+ 3 - 1
i18n/pl.json

@@ -482,5 +482,7 @@
     "templates.newTemplate": "Nowy szablon",
     "templates.newTemplate": "Nowy szablon",
     "templates.placeholderHelp": "Symbol zastępczy {placeholder} powinien występować dokładnie raz w szablonie.",
     "templates.placeholderHelp": "Symbol zastępczy {placeholder} powinien występować dokładnie raz w szablonie.",
     "templates.preview": "Podgląd",
     "templates.preview": "Podgląd",
-    "templates.rawHTML": "Surowy HTML"
+    "templates.rawHTML": "Surowy HTML",
+    "users.login": "Login",
+    "users.logout": "Logout"
 }
 }

+ 3 - 1
i18n/pt-BR.json

@@ -482,5 +482,7 @@
     "templates.newTemplate": "Novo modelo",
     "templates.newTemplate": "Novo modelo",
     "templates.placeholderHelp": "O palavra reservada {placeholder} deve aparecer exatamente uma vez no modelo.",
     "templates.placeholderHelp": "O palavra reservada {placeholder} deve aparecer exatamente uma vez no modelo.",
     "templates.preview": "Pré-visualizar",
     "templates.preview": "Pré-visualizar",
-    "templates.rawHTML": "Código HTML"
+    "templates.rawHTML": "Código HTML",
+    "users.login": "Login",
+    "users.logout": "Logout"
 }
 }

+ 3 - 1
i18n/pt.json

@@ -482,5 +482,7 @@
     "templates.newTemplate": "Novo template",
     "templates.newTemplate": "Novo template",
     "templates.placeholderHelp": "O placeholder {placeholder} deve aparecer exatamente uma vez no template.",
     "templates.placeholderHelp": "O placeholder {placeholder} deve aparecer exatamente uma vez no template.",
     "templates.preview": "Pré-visualização",
     "templates.preview": "Pré-visualização",
-    "templates.rawHTML": "HTML Simples"
+    "templates.rawHTML": "HTML Simples",
+    "users.login": "Login",
+    "users.logout": "Logout"
 }
 }

+ 3 - 1
i18n/ro.json

@@ -482,5 +482,7 @@
     "templates.newTemplate": "Template nou",
     "templates.newTemplate": "Template nou",
     "templates.placeholderHelp": "Substituentul {placeholder} ar trebui să apară exact o dată în șablon.",
     "templates.placeholderHelp": "Substituentul {placeholder} ar trebui să apară exact o dată în șablon.",
     "templates.preview": "Previzualizare",
     "templates.preview": "Previzualizare",
-    "templates.rawHTML": "HTML brut"
+    "templates.rawHTML": "HTML brut",
+    "users.login": "Login",
+    "users.logout": "Logout"
 }
 }

+ 3 - 1
i18n/ru.json

@@ -482,5 +482,7 @@
     "templates.newTemplate": "Новый шаблон",
     "templates.newTemplate": "Новый шаблон",
     "templates.placeholderHelp": "Заполнитель {placeholder} должен присутствовать в шаблоне в одном экземпляре.",
     "templates.placeholderHelp": "Заполнитель {placeholder} должен присутствовать в шаблоне в одном экземпляре.",
     "templates.preview": "Предпросмотр",
     "templates.preview": "Предпросмотр",
-    "templates.rawHTML": "Необработанный HTML"
+    "templates.rawHTML": "Необработанный HTML",
+    "users.login": "Login",
+    "users.logout": "Logout"
 }
 }

+ 3 - 1
i18n/tr.json

@@ -482,5 +482,7 @@
     "templates.newTemplate": "Yeni taslak",
     "templates.newTemplate": "Yeni taslak",
     "templates.placeholderHelp": "Yer tutucu {placeholder} taslak içinde sadece bir kere olmalıdır.",
     "templates.placeholderHelp": "Yer tutucu {placeholder} taslak içinde sadece bir kere olmalıdır.",
     "templates.preview": "Önizleme",
     "templates.preview": "Önizleme",
-    "templates.rawHTML": "Ham HTML"
+    "templates.rawHTML": "Ham HTML",
+    "users.login": "Login",
+    "users.logout": "Logout"
 }
 }

+ 5 - 0
static/public/static/style.css

@@ -46,6 +46,9 @@ input[type="text"], input[type="email"], select {
     border-color: #0055d4;
     border-color: #0055d4;
   }
   }
 
 
+.center {
+  text-align: center;
+}
 .button {
 .button {
   background: #0055d4;
   background: #0055d4;
   padding: 15px 30px;
   padding: 15px 30px;
@@ -57,9 +60,11 @@ input[type="text"], input[type="email"], select {
   display: inline-block;
   display: inline-block;
   min-width: 150px;
   min-width: 150px;
   font-size: 1.1em;
   font-size: 1.1em;
+  text-align: center;
 }
 }
 .button:hover {
 .button:hover {
   background: #333;
   background: #333;
+  color: #fff;
 }
 }
 .button.button-outline {
 .button.button-outline {
   background: #fff;
   background: #fff;