Add first version of the frontend

As of now the only usable functionality is signin in/out and managing
authorized users, and even then there is much room for improvement.

Change-Id: Ib87fc6866f69113a230187710de8644b78391917
diff --git a/frontend/src/pages/AuthorizedUsers.vue b/frontend/src/pages/AuthorizedUsers.vue
new file mode 100644
index 0000000..2f2b326
--- /dev/null
+++ b/frontend/src/pages/AuthorizedUsers.vue
@@ -0,0 +1,170 @@
+<script>
+import NotAuthorized from './NotAuthorized.vue';
+import Page from './utils/Page.vue';
+import AuthorizedUserDialog from '../components/AuthorizedUserDialog.vue';
+
+//import * as grpcWeb from "grpc-web";
+import * as ksObjectsPb from '../api_proto/kill_switch_objects_pb.js';
+import * as ksPb from '../api_proto/kill_switch_pb.js';
+import {accessLevels} from '../consts.js';
+
+export default {
+  data() {
+    let emptyUser = new ksObjectsPb.KillSwitchAuthorizedUser();
+
+    return {
+      users: [],
+      currentUpdateUser: emptyUser, // Current user being updated
+      currentDeleteUser: emptyUser, // Current user being confirmed deletion
+      addDialogOpen: false,
+      updateDialogOpen: false,
+      deleteDialogOpen: false,
+      accessLevels,
+    };
+  },
+  components: {
+    NotAuthorized,
+    Page,
+    AuthorizedUserDialog,
+  },
+  mounted() {
+    this.loadData();
+  },
+  methods: {
+    loadData() {
+      if (!this.isSignedIn) return;
+
+      let request = new ksPb.ListAuthorizedUsersRequest();
+
+      this.$store.state.client.listAuthorizedUsers(
+          request, {authorization: this.$store.state.jwtToken})
+          .then(res => {
+            this.$data.users = res.getUsersList();
+          })
+          .catch(err => console.error(err));
+    },
+    showAddForm(user) {
+      this.$data.addDialogOpen = true;
+    },
+    showUpdateForm(user) {
+      this.currentUpdateUser = user;
+      this.$data.updateDialogOpen = true;
+    },
+    showDeleteDialog(user) {
+      this.$data.currentDeleteUser = user;
+      this.$data.deleteDialogOpen = true;
+    },
+    deleteUser(user) {
+      let request = new ksPb.DeleteAuthorizedUserRequest();
+      request.setUserId(user.getId());
+
+      this.$store.state.client.deleteAuthorizedUser(
+          request, {authorization: this.$store.state.jwtToken})
+          .then(res => {
+            this.loadData();
+          })
+          .catch(err => console.error(err));
+    },
+  },
+  computed: {
+    isSignedIn() {
+      return this.$store.state.jwtToken != null;
+    },
+  },
+};
+</script>
+
+<template>
+  <template v-if="isSignedIn">
+    <div class="container">
+      <mcw-data-table>
+        <table class="mdc-data-table__table" aria-label="Authorized users">
+          <thead>
+            <tr class="mdc-data-table__header-row">
+              <th
+                  class="mdc-data-table__header-cell"
+                  role="columnheader"
+                  scope="col">
+                ID
+              </th>
+              <th
+                  class="mdc-data-table__header-cell"
+                  role="columnheader"
+                  scope="col">
+                Google UID
+              </th>
+              <th
+                  class="mdc-data-table__header-cell"
+                  role="columnheader"
+                  scope="col">
+                E-mail address
+              </th>
+              <th
+                  class="mdc-data-table__header-cell"
+                  role="columnheader"
+                  scope="col">
+                Scope
+              </th>
+              <th
+                  class="mdc-data-table__header-cell"
+                  role="columnheader"
+                  scope="col">
+              </th>
+            </tr>
+          </thead>
+          <tbody class="mdc-data-table__content">
+            <tr class="mdc-data-table__row" v-for="user in users">
+              <td class="mdc-data-table__cell mdc-data-table__cell--numeric">{{ user.getId() }}</td>
+              <td class="mdc-data-table__cell">{{ user.getGoogleUid() || "-" }}</td>
+              <td class="mdc-data-table__cell">{{ user.getEmail() || "-" }}</td>
+              <td class="mdc-data-table__cell">{{ accessLevels[user.getAccessLevel()] }}</td>
+              <td class="mdc-data-table__cell">
+                <mcw-icon-button @click="(showUpdateForm(user))"><mcw-material-icon icon="edit" /></mcw-icon-button>
+                <mcw-icon-button @click="(showDeleteDialog(user))"><mcw-material-icon icon="delete" /></mcw-icon-button>
+              </td>
+            </tr>
+          </tbody>
+        </table>
+      </mcw-data-table>
+    </div>
+    <mcw-fab @click="showAddForm()" class="add-user" icon="add" />
+    <mcw-dialog
+        v-model="deleteDialogOpen"
+        escape-key-action="close"
+        scrim-click-action="close"
+        aria-labelledby="delete-title"
+        aria-describedby="delete-content"
+        :auto-stack-buttons="true">
+      <mcw-dialog-title id="delete-title">Delete authorized user?</mcw-dialog-title>
+      <mcw-dialog-content id="delete-content">
+        <div>User {{ currentDeleteUser.getId() }} will no longer have access to the TW Power Tools dashboard.</div>
+      </mcw-dialog-content>
+      <mcw-dialog-footer>
+        <mcw-dialog-button action="dismiss">Cancel</mcw-dialog-button>
+        <mcw-dialog-button @click="deleteUser(currentDeleteUser)" action="accept">Delete</mcw-dialog-button>
+      </mcw-dialog-footer>
+    </mcw-dialog>
+    <!-- Add user dialog -->
+    <authorized-user-dialog @user-added="loadData()" v-model="addDialogOpen" />
+    <!-- Update user dialog -->
+    <authorized-user-dialog @user-updated="loadData()" v-model="updateDialogOpen" is-update :user="currentUpdateUser" />
+  </template>
+  <template v-else>
+    <not-authorized>
+    </not-authorized>
+  </template>
+</template>
+
+<style scoped>
+.container {
+  margin-top: 16px;
+  display: flex;
+  justify-content: space-evenly;
+}
+
+.add-user {
+  position: fixed;
+  right: 16px;
+  bottom: 16px;
+}
+</style>