Skip to content
This repository has been archived by the owner on Jul 12, 2023. It is now read-only.

Paginate mobile apps #955

Merged
merged 1 commit into from
Nov 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/server/assets/apikeys/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

<div class="card mt-4 mb-3">
<div class="card-header">
<span class="oi oi-key mr-2 ml-n1" aria-hidden="true"></span>
API keys
<a href="/apikeys/new" class="float-right mr-n1 text-secondary" data-toggle="tooltip" title="New API key">
<span class="oi oi-plus small" aria-hidden="true"></span>
Expand Down
2 changes: 1 addition & 1 deletion cmd/server/assets/mobileapps/edit.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
{{template "head" .}}
</head>

<body class="tab-content">
<body id="mobileapps-edit" class="tab-content">
{{template "navbar" .}}

<main role="main" class="container">
Expand Down
124 changes: 63 additions & 61 deletions cmd/server/assets/mobileapps/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,74 +8,76 @@
{{template "head" .}}
</head>

<body class="tab-content">
<body id="mobileapps-index" class="tab-content">
{{template "navbar" .}}

<main role="main" class="container">
{{template "flash" .}}

<h1>Mobile apps</h1>
<p>
These are the mobile apps for this realm. You
can also <a href="/mobile-apps/new">create a new mobile app</a>.
</p>
<div class="card mt-4 mb-3">
<div class="card-header">
<span class="oi oi-phone mr-2 ml-n1" aria-hidden="true"></span>
Mobile apps
<a href="/mobile-apps/new" class="float-right mr-n1 text-secondary" data-toggle="tooltip" title="New mobile app">
<span class="oi oi-plus small" aria-hidden="true"></span>
</a>
</div>

{{if .apps}}
<div class="table-responsive">
<table class="table table-bordered table-striped bg-white">
<thead>
<tr>
<th scope="col">App</th>
<th scope="col" width="95">Enabled</th>
<th scope="col" width="40"></th>
</tr>
</thead>
<tbody>
{{range .apps}}
<tr>

<td>
<a href="/mobile-apps/{{.ID}}" class="text-truncate">{{.Name}}</a>
</td>

<td>
{{if .DeletedAt}}
<span class="badge badge-pill badge-danger">Deleted</span>
{{else}}
<span class="badge badge-pill badge-success">Enabled</span>
{{end}}
</td>

<td class="text-center">
{{if .DeletedAt}}
<a href="/mobile-apps/{{.ID}}/enable" class="d-block text-danger"
data-method="patch"
data-confirm="Are you sure you want to restore '{{.Name}}'?"
data-toggle="tooltip"
title="Restore this mobile app">
<span class="oi oi-loop-circular" aria-hidden="true"></span>
</a>
{{else}}
<a href="/mobile-apps/{{.ID}}/disable" class="d-block text-danger"
data-method="patch"
data-confirm="Are you sure you want to disable '{{.Name}}'?"
data-toggle="tooltip"
title="Disable this mobile app">
<span class="oi oi-trash" aria-hidden="true"></span>
</a>
{{end}}
</td>

</tr>
{{end}}
</tbody>
</table>
{{if $apps}}
<table class="table table-bordered table-striped table-fixed table-inner-border-only mb-0">
<thead>
<tr>
<th scope="col" width="40"></th>
<th scope="col">Mobile app</th>
<th scope="col" width="40"></th>
</tr>
</thead>
<tbody>
{{range $apps}}
<tr>
<td class="text-center">
{{if .DeletedAt}}
<span class="oi oi-circle-x text-danger"
data-toggle="tooltip" title="Mobile app is disabled - it will be deleted in a few days"></span>
{{else}}
<span class="oi oi-circle-check text-success"
data-toggle="tooltip" title="Mobile app is enabled"></span>
{{end}}
</td>
<td class="text-truncate">
<a href="/mobile-apps/{{.ID}}">{{.Name}}</a>
</td>
<td class="text-center">
{{if .DeletedAt}}
<a href="/mobile-apps/{{.ID}}/enable" class="d-block text-danger"
data-method="patch"
data-confirm="Are you sure you want to restore '{{.Name}}'?"
data-toggle="tooltip"
title="Restore this mobile app">
<span class="oi oi-loop-circular" aria-hidden="true"></span>
</a>
{{else}}
<a href="/mobile-apps/{{.ID}}/disable" class="d-block text-danger"
data-method="patch"
data-confirm="Are you sure you want to disable '{{.Name}}'?"
data-toggle="tooltip"
title="Disable this mobile app">
<span class="oi oi-trash" aria-hidden="true"></span>
</a>
{{end}}
</td>
</tr>
{{end}}
</tbody>
</table>
{{else}}
<p class="text-center mb-0">
<em>There are no mobile apps.</em>
</p>
{{end}}
</div>
{{else}}
<p class="text-center">
<em>There are no mobile apps.</em>
</p>
{{end}}

{{template "shared/pagination" .}}
</main>
</body>
</html>
Expand Down
4 changes: 2 additions & 2 deletions cmd/server/assets/mobileapps/new.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
{{template "head" .}}
</head>

<body class="tab-content">
<body id="mobileapps-new" class="tab-content">
{{template "navbar" .}}

<main role="main" class="container">
Expand All @@ -27,7 +27,7 @@ <h1>New mobile app</h1>

{{template "mobileapps/_app" .}}

<button type="submit" class="btn btn-primary btn-block">Create mobile app</button>
<button type="submit" id="submit" class="btn btn-primary btn-block">Create mobile app</button>
</form>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion cmd/server/assets/mobileapps/show.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
{{template "head" .}}
</head>

<body class="tab-content">
<body id="mobileapps-show" class="tab-content">
{{template "navbar" .}}

<main role="main" class="container">
Expand Down
1 change: 1 addition & 0 deletions pkg/controller/mobileapps/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ func (c *Controller) HandleCreate() http.Handler {
// renderNew renders the new page.
func (c *Controller) renderNew(ctx context.Context, w http.ResponseWriter, app *database.MobileApp) {
m := templateMap(ctx)
m["title"] = fmt.Sprintf("New mobile app - %s", m["title"])
m["app"] = app
c.h.RenderHTML(w, "mobileapps/new", m)
}
96 changes: 96 additions & 0 deletions pkg/controller/mobileapps/create_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package mobileapps_test

import (
"context"
"strconv"
"testing"
"time"

"github.com/google/exposure-notifications-verification-server/internal/browser"
"github.com/google/exposure-notifications-verification-server/internal/envstest"
"github.com/google/exposure-notifications-verification-server/pkg/controller"
"github.com/google/exposure-notifications-verification-server/pkg/database"

"github.com/chromedp/chromedp"
)

func TestHandleCreate(t *testing.T) {
t.Parallel()

harness := envstest.NewServer(t)

// Get the default realm
realm, err := harness.Database.FindRealm(1)
if err != nil {
t.Fatal(err)
}

// Create a user
admin := &database.User{
Email: "admin@example.com",
Name: "Admin",
Realms: []*database.Realm{realm},
AdminRealms: []*database.Realm{realm},
}
if err := harness.Database.SaveUser(admin, database.System); err != nil {
t.Fatal(err)
}

// Log in the user.
session, err := harness.LoggedInSession(nil, admin.Email)
if err != nil {
t.Fatal(err)
}

// Set the current realm.
controller.StoreSessionRealm(session, realm)

// Mint a cookie for the session.
cookie, err := harness.SessionCookie(session)
if err != nil {
t.Fatal(err)
}
// Create a browser runner.
browserCtx := browser.New(t)
taskCtx, done := context.WithTimeout(browserCtx, 30*time.Second)
defer done()

if err := chromedp.Run(taskCtx,
// Pre-authenticate the user.
browser.SetCookie(cookie),

// Visit /apikeys/new.
chromedp.Navigate(`http://`+harness.Server.Addr()+`/mobile-apps/new`),

// Wait for render.
chromedp.WaitVisible(`body#mobileapps-new`, chromedp.ByQuery),

// Fill out the form.
chromedp.SetValue(`input#name`, "Example mobile app", chromedp.ByQuery),
chromedp.SetValue(`input#url`, "https://example.com", chromedp.ByQuery),
chromedp.SetValue(`select#os`, strconv.Itoa(int(database.OSTypeIOS)), chromedp.ByQuery),
chromedp.SetValue(`input#app-id`, "com.example.app", chromedp.ByQuery),

// Click the submit button.
chromedp.Click(`#submit`, chromedp.ByQuery),

// Wait for the page to reload.
chromedp.WaitVisible(`body#mobileapps-show`, chromedp.ByQuery),
); err != nil {
t.Fatal(err)
}
}
16 changes: 13 additions & 3 deletions pkg/controller/mobileapps/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ package mobileapps

import (
"context"
"fmt"
"net/http"

"github.com/google/exposure-notifications-verification-server/pkg/controller"
"github.com/google/exposure-notifications-verification-server/pkg/database"
"github.com/google/exposure-notifications-verification-server/pkg/pagination"
)

func (c *Controller) HandleIndex() http.Handler {
Expand All @@ -32,20 +34,28 @@ func (c *Controller) HandleIndex() http.Handler {
return
}

pageParams, err := pagination.FromRequest(r)
if err != nil {
controller.InternalError(w, r, c.h, err)
return
}

// Perform the lazy load on authorized apps for the realm.
apps, err := realm.ListMobileApps(c.db)
apps, paginator, err := realm.ListMobileApps(c.db, pageParams)
if err != nil {
controller.InternalError(w, r, c.h, err)
return
}

c.renderIndex(ctx, w, apps)
c.renderIndex(ctx, w, apps, paginator)
})
}

// renderIndex renders the index page.
func (c *Controller) renderIndex(ctx context.Context, w http.ResponseWriter, apps []*database.MobileApp) {
func (c *Controller) renderIndex(ctx context.Context, w http.ResponseWriter, apps []*database.MobileApp, paginator *pagination.Paginator) {
m := templateMap(ctx)
m["title"] = fmt.Sprintf("Mobile apps - %s", m["title"])
m["apps"] = apps
m["paginator"] = paginator
c.h.RenderHTML(w, "mobileapps/index", m)
}
2 changes: 2 additions & 0 deletions pkg/controller/mobileapps/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package mobileapps

import (
"context"
"fmt"
"net/http"

"github.com/google/exposure-notifications-verification-server/pkg/controller"
Expand Down Expand Up @@ -61,6 +62,7 @@ func (c *Controller) HandleShow() http.Handler {
// renderShow renders the edit page.
func (c *Controller) renderShow(ctx context.Context, w http.ResponseWriter, app *database.MobileApp) {
m := templateMap(ctx)
m["title"] = fmt.Sprintf("%s - Mobile apps - %s", app.Name, m["title"])
m["app"] = app
c.h.RenderHTML(w, "mobileapps/show", m)
}
2 changes: 2 additions & 0 deletions pkg/controller/mobileapps/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package mobileapps

import (
"context"
"fmt"
"net/http"

"github.com/google/exposure-notifications-verification-server/pkg/controller"
Expand Down Expand Up @@ -110,6 +111,7 @@ func (c *Controller) HandleUpdate() http.Handler {
// renderEdit renders the edit page.
func (c *Controller) renderEdit(ctx context.Context, w http.ResponseWriter, app *database.MobileApp) {
m := templateMap(ctx)
m["title"] = fmt.Sprintf("%s - Edit mobile app - %s", app.Name, m["title"])
m["app"] = app
c.h.RenderHTML(w, "mobileapps/edit", m)
}
Loading