Pārlūkot izejas kodu

Merge pull request #141 from simple-login/dashboard-ui

Better dashboard ui, global stats
Son Nguyen Kim 5 gadi atpakaļ
vecāks
revīzija
f8ba0d954f

+ 147 - 58
app/dashboard/templates/dashboard/index.html

@@ -20,6 +20,61 @@
 {% endblock %}
 
 {% block default_content %}
+  <!-- Global Stats -->
+  <div class="row">
+    <div class="col-6 col-sm-4 col-lg-2">
+      <div class="card">
+        <div class="card-body p-3 text-center">
+          <div class="h1 m-0 mt-3">{{ stats.nb_alias }}</div>
+          <div class="text-muted mb-4">Aliases</div>
+        </div>
+      </div>
+    </div>
+    <div class="col-6 col-sm-4 col-lg-2">
+      <div class="card">
+        <div class="card-body p-3 text-center">
+          <div class="h1 m-0 mt-3">{{ stats.nb_active_alias }}</div>
+          <div class="text-muted mb-4">Active Aliases</div>
+        </div>
+      </div>
+    </div>
+    <div class="col-6 col-sm-4 col-lg-2">
+      <div class="card">
+        <div class="card-body p-3 text-center">
+          <div class="h1 m-0 mt-3">{{ stats.nb_forward }}</div>
+          <div class="text-muted mb-4">Forwards</div>
+        </div>
+      </div>
+    </div>
+    <div class="col-6 col-sm-4 col-lg-2">
+      <div class="card">
+        <div class="card-body p-3 text-center">
+          <div class="h1 m-0 mt-3">{{ stats.nb_reply }}</div>
+          <div class="text-muted mb-4">Replies</div>
+        </div>
+      </div>
+    </div>
+    <div class="col-6 col-sm-4 col-lg-2">
+      <div class="card">
+        <div class="card-body p-3 text-center">
+          <div class="h1 m-0 mt-3">{{ stats.nb_directory }}</div>
+          <div class="text-muted mb-4">Directories</div>
+        </div>
+      </div>
+    </div>
+    <div class="col-6 col-sm-4 col-lg-2">
+      <div class="card">
+        <div class="card-body p-3 text-center">
+          <div class="h1 m-0 mt-3">{{ stats.nb_domain }}</div>
+          <div class="text-muted mb-4">Domains</div>
+        </div>
+      </div>
+    </div>
+
+  </div>
+  <!-- END Global Stats -->
+
+
   <div class="row mb-3">
 
     <div class="col-lg-6 pt-1" style="max-width: 25em">
@@ -62,53 +117,65 @@
       </div>
     </div>
 
-    <div class="col-lg-auto pt-1 flex-grow-1">
-      <div class="float-right">
-        <form method="get" class="form-inline">
-          <select name="sort"
-                  onchange="this.form.submit()"
-                  class="form-control custom-select mr-3 shadow">
-            <option value="" {% if sort == "" %} selected {% endif %}>
-              Sort by most recent activity
-            </option>
-            <option value="old2new" {% if sort == "old2new" %} selected {% endif %}>
-              Alias Old-Recent
-            </option>
-            <option value="old2new" {% if sort == "new2old" %} selected {% endif %}>
-              Alias Recent-Old
-            </option>
-            <option value="a2z" {% if sort == "a2z" %} selected {% endif %}>
-              Alias A-Z
-            </option>
-            <option value="z2a" {% if sort == "z2a" %} selected {% endif %}>
-              Alias Z-A
-            </option>
-          </select>
-
-          <select name="filter"
-                  onchange="this.form.submit()"
-                  class="form-control custom-select mr-3 shadow">
-            <option value="" {% if filter == "" %} selected {% endif %}>
-              All Aliases
-            </option>
-            <option value="enabled" {% if filter == "enabled" %} selected {% endif %}>
-              Only Enabled Aliases
-            </option>
-            <option value="disabled" {% if filter == "disabled" %} selected {% endif %}>
-              Only Disabled Aliases
-            </option>
-          </select>
-
-          <input type="search" name="query" placeholder="Enter to search for alias"
-                 class="form-control shadow mr-2"
-                 style="max-width: 15em"
-                 value="{{ query }}">
-
-          {% if query or sort or filter %}
-            <a href="{{ url_for('dashboard.index') }}"
-               class="btn btn-light">Reset</a>
-          {% endif %}
-        </form>
+    <div id="filter-app" class="col-lg-auto pt-1 flex-grow-1">
+      <div class="float-right d-flex">
+
+        <!-- Filter Control -->
+        <div v-if="showFilter" id="filter-control">
+          <form method="get" class="form-inline">
+            <select name="sort"
+                    onchange="this.form.submit()"
+                    class="form-control custom-select mr-3 shadow">
+              <option value="" {% if sort == "" %} selected {% endif %}>
+                Sort by most recent activity
+              </option>
+              <option value="old2new" {% if sort == "old2new" %} selected {% endif %}>
+                Alias Old-Recent
+              </option>
+              <option value="old2new" {% if sort == "new2old" %} selected {% endif %}>
+                Alias Recent-Old
+              </option>
+              <option value="a2z" {% if sort == "a2z" %} selected {% endif %}>
+                Alias A-Z
+              </option>
+              <option value="z2a" {% if sort == "z2a" %} selected {% endif %}>
+                Alias Z-A
+              </option>
+            </select>
+
+            <select name="filter"
+                    onchange="this.form.submit()"
+                    class="form-control custom-select mr-3 shadow">
+              <option value="" {% if filter == "" %} selected {% endif %}>
+                All Aliases
+              </option>
+              <option value="enabled" {% if filter == "enabled" %} selected {% endif %}>
+                Only Enabled Aliases
+              </option>
+              <option value="disabled" {% if filter == "disabled" %} selected {% endif %}>
+                Only Disabled Aliases
+              </option>
+            </select>
+
+            <input type="search" name="query" placeholder="Enter to search for alias"
+                   class="form-control shadow mr-2"
+                   style="max-width: 15em"
+                   value="{{ query }}">
+
+            {% if query or sort or filter %}
+              <a href="{{ url_for('dashboard.index') }}"
+                 class="btn btn-light">Reset</a>
+            {% endif %}
+          </form>
+        </div>
+
+        <a v-if="!showFilter" @click="toggleFilter()" class="btn btn-secondary">
+          <i class="fe fe-chevrons-left"></i> Filters
+        </a>
+
+        <a v-if="showFilter" @click="toggleFilter()" class="btn btn-outline-secondary">
+          <i class="fe fe-chevrons-right"></i>
+        </a>
       </div>
     </div>
   </div>
@@ -169,7 +236,7 @@
           </div>
 
           <!-- Email Activity -->
-          <div class="row">
+          <div class="row mb-2">
             <div class="col">
               <div style="font-size: 12px">
                 {% if alias_info.latest_email_log != None %}
@@ -199,15 +266,7 @@
                 {% else %}
                   No Activity. Alias created {{ alias.created_at | dt }}
                 {% endif %}
-                <br>
-
-                <span class="alias-activity">{{ alias_info.nb_forward }}</span> forwards,
-                <span class="alias-activity">{{ alias_info.nb_blocked }}</span> blocks,
-                <span class="alias-activity">{{ alias_info.nb_reply }}</span> replies
-                <a href="{{ url_for('dashboard.alias_log', alias_id=alias.id) }}"
-                   class="btn btn-sm btn-link">
-                  See All &nbsp;→
-                </a>
+
               </div>
             </div>
           </div>
@@ -250,6 +309,14 @@
               </div>
             {% endif %}
 
+            <span class="alias-activity">{{ alias_info.nb_forward }}</span> forwards,
+            <span class="alias-activity">{{ alias_info.nb_blocked }}</span> blocks,
+            <span class="alias-activity">{{ alias_info.nb_reply }}</span> replies
+            <a href="{{ url_for('dashboard.alias_log', alias_id=alias.id) }}"
+               class="btn btn-sm btn-link">
+              See All &nbsp;→
+            </a>
+
             {% if mailboxes|length > 1 %}
               <div class="small-text">Current mailbox</div>
               <div class="d-flex">
@@ -336,7 +403,7 @@
 
             </div>
           </div>
-
+          <!-- END Collapse section -->
         </div>
       </div>
     {% endfor %}
@@ -582,4 +649,26 @@
 
     })
   </script>
+
+  <script src="{{ url_for('static', filename='node_modules/vue/dist/vue.min.js') }}"></script>
+  <script>
+    var app = new Vue({
+      el: '#filter-app',
+      delimiters: ["[[", "]]"], // necessary to avoid conflict with jinja
+      data: {
+        showFilter: false
+      },
+      methods: {
+        async toggleFilter() {
+          let that = this;
+          that.showFilter = !that.showFilter;
+          store.set('showFilter', that.showFilter);
+        }
+      },
+      async mounted() {
+        if (store.get("showFilter"))
+          this.showFilter = true;
+      }
+    })
+  </script>
 {% endblock %}

+ 41 - 0
app/dashboard/views/index.py

@@ -1,3 +1,5 @@
+from dataclasses import dataclass
+
 from flask import render_template, request, redirect, url_for, flash
 from flask_login import login_required, current_user
 from sqlalchemy.exc import IntegrityError
@@ -12,9 +14,45 @@ from app.models import (
     ClientUser,
     DeletedAlias,
     AliasGeneratorEnum,
+    User,
+    EmailLog,
+    CustomDomain,
+    Directory,
 )
 
 
+@dataclass
+class Stats:
+    nb_alias: int
+    nb_active_alias: int
+    nb_forward: int
+    nb_reply: int
+    nb_domain: int
+    nb_directory: int
+
+
+def get_stats(user: User) -> Stats:
+    nb_alias = Alias.query.filter_by(user_id=user.id).count()
+    nb_active_alias = Alias.query.filter_by(user_id=user.id, enabled=True).count()
+    nb_forward = EmailLog.query.filter_by(
+        user_id=user.id, is_reply=False, blocked=False, bounced=False
+    ).count()
+    nb_reply = EmailLog.query.filter_by(
+        user_id=user.id, is_reply=True, blocked=False, bounced=False
+    ).count()
+    nb_domain = CustomDomain.query.filter_by(user_id=user.id).count()
+    nb_directory = Directory.query.filter_by(user_id=user.id).count()
+
+    data = locals()
+    # to keep only Stats field
+    data = {
+        k: v
+        for (k, v) in data.items()
+        if k in vars(Stats)["__dataclass_fields__"].keys()
+    }
+    return Stats(**data)
+
+
 @dashboard_bp.route("/", methods=["GET", "POST"])
 @login_required
 def index():
@@ -120,6 +158,8 @@ def index():
         current_user.intro_shown = True
         db.session.commit()
 
+    stats = get_stats(current_user)
+
     return render_template(
         "dashboard/index.html",
         client_users=client_users,
@@ -134,4 +174,5 @@ def index():
         page=page,
         sort=sort,
         filter=alias_filter,
+        stats=stats,
     )

+ 5 - 0
static/package-lock.json

@@ -98,6 +98,11 @@
       "version": "1.10.0",
       "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
       "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ=="
+    },
+    "vue": {
+      "version": "2.6.11",
+      "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.11.tgz",
+      "integrity": "sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ=="
     }
   }
 }

+ 2 - 1
static/package.json

@@ -21,6 +21,7 @@
     "intro.js": "^2.9.3",
     "notie": "^4.3.1",
     "qrious": "^4.0.2",
-    "toastr": "^2.1.4"
+    "toastr": "^2.1.4",
+    "vue": "^2.6.11"
   }
 }

+ 4 - 4
templates/menu.html

@@ -3,7 +3,7 @@
     <a href="{{ url_for('dashboard.index') }}"
        class="nav-link {{ 'active' if active_page == 'dashboard' }}">
       <i class="fe fe-home"></i>
-      Alias
+      Aliases
     </a>
   </li>
 
@@ -11,21 +11,21 @@
     <a href="{{ url_for('dashboard.api_key') }}"
        class="nav-link {{ 'active' if active_page == 'api_key' }}">
       <i><img src="/static/key.svg"></i>
-      API Key
+      API Keys
     </a>
   </li>
 
   <li class="nav-item">
     <a href="{{ url_for('dashboard.custom_domain') }}"
        class="nav-link {{ 'active' if active_page == 'custom_domain' }}">
-      <i class="fe fe-server"></i> Custom Domains
+      <i class="fe fe-server"></i> Domains
     </a>
   </li>
 
   <li class="nav-item">
     <a href="{{ url_for('dashboard.directory') }}"
        class="nav-link {{ 'active' if active_page == 'directory' }}">
-      <i class="fe fe-folder"></i> Alias Directory
+      <i class="fe fe-folder"></i> Directories
     </a>
   </li>