소스 검색

added vuetify framework and implemented login and logout views

Dimitri Korsch 3 년 전
부모
커밋
d095de6e83

+ 240 - 10
frontend/package-lock.json

@@ -2547,8 +2547,7 @@
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
       "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
-      "dev": true,
-      "optional": true
+      "dev": true
     },
     "bluebird": {
       "version": "3.7.2",
@@ -2916,6 +2915,12 @@
         "caller-callsite": "^2.0.0"
       }
     },
+    "callsite": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz",
+      "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=",
+      "dev": true
+    },
     "callsites": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz",
@@ -2996,7 +3001,6 @@
       "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz",
       "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==",
       "dev": true,
-      "optional": true,
       "requires": {
         "anymatch": "~3.1.2",
         "braces": "~3.0.2",
@@ -3013,7 +3017,6 @@
           "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
           "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
           "dev": true,
-          "optional": true,
           "requires": {
             "fill-range": "^7.0.1"
           }
@@ -3023,7 +3026,6 @@
           "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
           "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
           "dev": true,
-          "optional": true,
           "requires": {
             "to-regex-range": "^5.0.1"
           }
@@ -3032,15 +3034,13 @@
           "version": "7.0.0",
           "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
           "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "to-regex-range": {
           "version": "5.0.1",
           "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
           "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
           "dev": true,
-          "optional": true,
           "requires": {
             "is-number": "^7.0.0"
           }
@@ -4037,6 +4037,15 @@
         "ms": "2.1.2"
       }
     },
+    "decache": {
+      "version": "4.6.0",
+      "resolved": "https://registry.npmjs.org/decache/-/decache-4.6.0.tgz",
+      "integrity": "sha512-PppOuLiz+DFeaUvFXEYZjLxAkKiMYH/do/b/MxpDe/8AgKBi5GhZxridoVIbBq72GDbL36e4p0Ce2jTGUwwU+w==",
+      "dev": true,
+      "requires": {
+        "callsite": "^1.0.0"
+      }
+    },
     "decamelize": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
@@ -6389,6 +6398,12 @@
         "side-channel": "^1.0.4"
       }
     },
+    "interpret": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz",
+      "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==",
+      "dev": true
+    },
     "ip": {
       "version": "1.1.5",
       "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
@@ -6463,7 +6478,6 @@
       "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
       "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
       "dev": true,
-      "optional": true,
       "requires": {
         "binary-extensions": "^2.0.0"
       }
@@ -6919,6 +6933,12 @@
       "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
       "dev": true
     },
+    "klona": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz",
+      "integrity": "sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==",
+      "dev": true
+    },
     "launch-editor": {
       "version": "2.2.1",
       "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.2.1.tgz",
@@ -7636,6 +7656,40 @@
         "boolbase": "~1.0.0"
       }
     },
+    "null-loader": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/null-loader/-/null-loader-4.0.1.tgz",
+      "integrity": "sha512-pxqVbi4U6N26lq+LmgIbB5XATP0VdZKOG25DhHi8btMmJJefGArFyDg1yc4U3hWCJbMqSrw0qyrz1UQX+qYXqg==",
+      "dev": true,
+      "requires": {
+        "loader-utils": "^2.0.0",
+        "schema-utils": "^3.0.0"
+      },
+      "dependencies": {
+        "loader-utils": {
+          "version": "2.0.2",
+          "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz",
+          "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==",
+          "dev": true,
+          "requires": {
+            "big.js": "^5.2.2",
+            "emojis-list": "^3.0.0",
+            "json5": "^2.1.2"
+          }
+        },
+        "schema-utils": {
+          "version": "3.1.1",
+          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
+          "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
+          "dev": true,
+          "requires": {
+            "@types/json-schema": "^7.0.8",
+            "ajv": "^6.12.5",
+            "ajv-keywords": "^3.5.2"
+          }
+        }
+      }
+    },
     "num2fraction": {
       "version": "1.2.2",
       "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz",
@@ -9028,11 +9082,19 @@
       "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
       "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
       "dev": true,
-      "optional": true,
       "requires": {
         "picomatch": "^2.2.1"
       }
     },
+    "rechoir": {
+      "version": "0.6.2",
+      "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
+      "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=",
+      "dev": true,
+      "requires": {
+        "resolve": "^1.1.6"
+      }
+    },
     "regenerate": {
       "version": "1.4.2",
       "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@@ -9411,6 +9473,76 @@
       "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
       "dev": true
     },
+    "sass": {
+      "version": "1.32.13",
+      "resolved": "https://registry.npmjs.org/sass/-/sass-1.32.13.tgz",
+      "integrity": "sha512-dEgI9nShraqP7cXQH+lEXVf73WOPCse0QlFzSD8k+1TcOxCMwVXfQlr0jtoluZysQOyJGnfr21dLvYKDJq8HkA==",
+      "dev": true,
+      "requires": {
+        "chokidar": ">=3.0.0 <4.0.0"
+      }
+    },
+    "sass-loader": {
+      "version": "10.2.0",
+      "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-10.2.0.tgz",
+      "integrity": "sha512-kUceLzC1gIHz0zNJPpqRsJyisWatGYNFRmv2CKZK2/ngMJgLqxTbXwe/hJ85luyvZkgqU3VlJ33UVF2T/0g6mw==",
+      "dev": true,
+      "requires": {
+        "klona": "^2.0.4",
+        "loader-utils": "^2.0.0",
+        "neo-async": "^2.6.2",
+        "schema-utils": "^3.0.0",
+        "semver": "^7.3.2"
+      },
+      "dependencies": {
+        "loader-utils": {
+          "version": "2.0.2",
+          "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz",
+          "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==",
+          "dev": true,
+          "requires": {
+            "big.js": "^5.2.2",
+            "emojis-list": "^3.0.0",
+            "json5": "^2.1.2"
+          }
+        },
+        "lru-cache": {
+          "version": "6.0.0",
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+          "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+          "dev": true,
+          "requires": {
+            "yallist": "^4.0.0"
+          }
+        },
+        "schema-utils": {
+          "version": "3.1.1",
+          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
+          "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
+          "dev": true,
+          "requires": {
+            "@types/json-schema": "^7.0.8",
+            "ajv": "^6.12.5",
+            "ajv-keywords": "^3.5.2"
+          }
+        },
+        "semver": {
+          "version": "7.3.5",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
+          "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
+          "dev": true,
+          "requires": {
+            "lru-cache": "^6.0.0"
+          }
+        },
+        "yallist": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+          "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+          "dev": true
+        }
+      }
+    },
     "sax": {
       "version": "1.2.4",
       "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
@@ -9650,6 +9782,17 @@
       "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==",
       "dev": true
     },
+    "shelljs": {
+      "version": "0.8.4",
+      "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz",
+      "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==",
+      "dev": true,
+      "requires": {
+        "glob": "^7.0.0",
+        "interpret": "^1.0.0",
+        "rechoir": "^0.6.2"
+      }
+    },
     "side-channel": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
@@ -11014,6 +11157,43 @@
       "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.14.tgz",
       "integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ=="
     },
+    "vue-cli-plugin-vuetify": {
+      "version": "2.4.4",
+      "resolved": "https://registry.npmjs.org/vue-cli-plugin-vuetify/-/vue-cli-plugin-vuetify-2.4.4.tgz",
+      "integrity": "sha512-asc6G7+yN7NCk2ha0zaaeVZ+hSUP++Akao99SeW8rPO/nCxN2AIeCZg7PHPHDvcDYL9+KWhTsw5YbqYCk8x4LA==",
+      "dev": true,
+      "requires": {
+        "null-loader": "^4.0.1",
+        "semver": "^7.1.2",
+        "shelljs": "^0.8.3"
+      },
+      "dependencies": {
+        "lru-cache": {
+          "version": "6.0.0",
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+          "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+          "dev": true,
+          "requires": {
+            "yallist": "^4.0.0"
+          }
+        },
+        "semver": {
+          "version": "7.3.5",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
+          "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
+          "dev": true,
+          "requires": {
+            "lru-cache": "^6.0.0"
+          }
+        },
+        "yallist": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+          "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+          "dev": true
+        }
+      }
+    },
     "vue-eslint-parser": {
       "version": "7.11.0",
       "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.11.0.tgz",
@@ -11188,6 +11368,56 @@
       "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
       "dev": true
     },
+    "vuetify": {
+      "version": "2.6.0",
+      "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.6.0.tgz",
+      "integrity": "sha512-a3gClGGY8FYIHvHtqDDmVnmTg8D5pPPcs3HYqdrmJ7b3RrF+bWlpqpAhUPVvD9ByYYWVKba4viAlhOc74meGsg=="
+    },
+    "vuetify-loader": {
+      "version": "1.7.3",
+      "resolved": "https://registry.npmjs.org/vuetify-loader/-/vuetify-loader-1.7.3.tgz",
+      "integrity": "sha512-1Kt6Rfvuw3i9BBlxC9WTMnU3WEU7IBWQmDX+fYGAVGpzWCX7oHythUIwPCZGShHSYcPMKSDbXTPP8UvT5RNw8Q==",
+      "dev": true,
+      "requires": {
+        "decache": "^4.6.0",
+        "file-loader": "^6.2.0",
+        "loader-utils": "^2.0.0"
+      },
+      "dependencies": {
+        "file-loader": {
+          "version": "6.2.0",
+          "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz",
+          "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==",
+          "dev": true,
+          "requires": {
+            "loader-utils": "^2.0.0",
+            "schema-utils": "^3.0.0"
+          }
+        },
+        "loader-utils": {
+          "version": "2.0.2",
+          "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz",
+          "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==",
+          "dev": true,
+          "requires": {
+            "big.js": "^5.2.2",
+            "emojis-list": "^3.0.0",
+            "json5": "^2.1.2"
+          }
+        },
+        "schema-utils": {
+          "version": "3.1.1",
+          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
+          "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
+          "dev": true,
+          "requires": {
+            "@types/json-schema": "^7.0.8",
+            "ajv": "^6.12.5",
+            "ajv-keywords": "^3.5.2"
+          }
+        }
+      }
+    },
     "vuex": {
       "version": "3.6.2",
       "resolved": "https://registry.npmjs.org/vuex/-/vuex-3.6.2.tgz",

+ 6 - 1
frontend/package.json

@@ -12,6 +12,7 @@
     "core-js": "^3.6.5",
     "vue": "^2.6.11",
     "vue-router": "^3.5.3",
+    "vuetify": "^2.4.0",
     "vuex": "^3.6.2"
   },
   "devDependencies": {
@@ -21,7 +22,11 @@
     "babel-eslint": "^10.1.0",
     "eslint": "^6.7.2",
     "eslint-plugin-vue": "^6.2.2",
-    "vue-template-compiler": "^2.6.11"
+    "sass": "~1.32.0",
+    "sass-loader": "^10.0.0",
+    "vue-cli-plugin-vuetify": "~2.4.4",
+    "vue-template-compiler": "^2.6.11",
+    "vuetify-loader": "^1.7.0"
   },
   "eslintConfig": {
     "root": true,

+ 3 - 1
frontend/public/index.html

@@ -1,11 +1,13 @@
 <!DOCTYPE html>
-<html lang="">
+<html lang="en">
   <head>
     <meta charset="utf-8">
     <meta http-equiv="X-UA-Compatible" content="IE=edge">
     <meta name="viewport" content="width=device-width,initial-scale=1.0">
     <link rel="icon" href="<%= BASE_URL %>favicon.ico">
     <title><%= htmlWebpackPlugin.options.title %></title>
+    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
+    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css">
   </head>
   <body>
     <noscript>

+ 31 - 7
frontend/src/App.vue

@@ -1,14 +1,38 @@
 <template>
-  <div id="app">
-    <router-view/>
-  </div>
+  <v-app id="pycs2">
+<!--
+    <v-app-bar app>
+      PyCSv2
+    </v-app-bar>
+ -->
+    <Drawer v-model="drawer"/>
+
+    <v-main>
+      <v-container fluid>
+        <router-view/>
+      </v-container>
+    </v-main>
+
+
+    <v-footer app>
+      <!-- -->
+    </v-footer>
+
+  </v-app>
 </template>
 
 <script>
+import Drawer from "@/components/Drawer"
+
 export default {
   name: 'App',
-}
-</script>
 
-<style>
-</style>
+  components: {
+    Drawer
+  },
+
+  data: () => ({
+    drawer: false,
+  }),
+};
+</script>

+ 89 - 0
frontend/src/components/Drawer.vue

@@ -0,0 +1,89 @@
+<template>
+  <v-navigation-drawer
+    permanent
+    app
+  >
+    <v-sheet
+      color="grey lighten-4"
+      class="pa-4"
+    >
+      <router-link :to="{ name: 'index' }">
+      <v-avatar
+        class="mb-4"
+        color="grey darken-1"
+        size="64"
+      ></v-avatar>
+
+      </router-link>
+
+      <div class="row">
+        <div class="col-md-6">{{ username }}</div>
+        <div class="col-md-3">
+          <div v-if="accessToken != null">
+            <v-btn :to = "{ name: 'logout' }" small primary>
+              <span>Logout</span>
+              <v-icon>mdi-logout</v-icon>
+            </v-btn>
+          </div>
+        </div>
+      </div>
+    </v-sheet>
+
+    <v-divider></v-divider>
+
+    <v-list v-if="accessToken != null" >
+      <v-list-item
+        v-for="[icon, text, dest] in links"
+        :key="icon"
+        :to="{name: dest}"
+        link
+      >
+        <v-list-item-icon>
+          <v-icon>{{ icon }}</v-icon>
+        </v-list-item-icon>
+
+        <v-list-item-content>
+          <v-list-item-title>{{ text }}</v-list-item-title>
+        </v-list-item-content>
+      </v-list-item>
+    </v-list>
+  </v-navigation-drawer>
+</template>
+
+<script>
+import { mapState } from 'vuex'
+export default {
+  name: "Drawer",
+  computed: mapState(["accessToken", "username"]),
+  data: () => ({
+    links: [
+      [
+        'mdi-inbox-arrow-down',
+        'Projects',
+        'projects'
+      ],
+      // [
+      //   'mdi-send',
+      //   'Send',
+      //   'foo'
+      // ],
+      // [
+      //   'mdi-delete',
+      //   'Trash',
+      //   'index'
+      // ],
+      // [
+      //   'mdi-alert-octagon',
+      //   'Spam',
+      //   'index'
+      // ],
+    ],
+  }),
+
+}
+
+</script>
+
+<style scoped>
+
+</style>

+ 3 - 1
frontend/src/main.js

@@ -2,6 +2,7 @@ import Vue from 'vue'
 import App from './App.vue'
 import router from './routes'
 import store from './store'
+import vuetify from './plugins/vuetify'
 
 Vue.config.productionTip = false
 
@@ -24,5 +25,6 @@ router.beforeEach((to, from, next) => {
 new Vue({
   router,
   store,
-  render: h => h(App),
+  vuetify,
+  render: h => h(App)
 }).$mount('#app')

+ 10 - 0
frontend/src/plugins/vuetify.js

@@ -0,0 +1,10 @@
+import Vue from 'vue';
+import Vuetify from 'vuetify/lib/framework';
+
+Vue.use(Vuetify);
+
+export default new Vuetify({
+  icons: {
+    iconfont: 'mdi', // 'mdi' || 'mdiSvg' || 'md' || 'fa' || 'fa4' || 'faSvg'
+  },
+});

+ 9 - 0
frontend/src/routes.js

@@ -1,6 +1,7 @@
 import Vue from 'vue'
 import VueRouter from 'vue-router'
 import Index from './views/Index'
+import Projects from './views/Projects'
 import Login from './views/Login'
 import Logout from './views/Logout'
 
@@ -21,6 +22,14 @@ export default new VueRouter({
                 requiresLogin: true
             }
         },
+        {
+            path: '/projects',
+            name: 'projects',
+            component: Projects,
+            meta: {
+                requiresLogin: true
+            }
+        },
         {
             path: '/login',
             name: 'login',

+ 11 - 4
frontend/src/store.js

@@ -8,30 +8,36 @@ export default new Vuex.Store({
   state: {
      accessToken: null,
      refreshToken: null,
+
+     username: null,
   },
 
   mutations: {
-    updateStorage (state, { access, refresh }) {
+    updateStorage (state, { access, refresh, username }) {
       state.accessToken = access
       state.refreshToken = refresh
+      state.username = username
 
       localStorage.setItem('accessToken', access)
       localStorage.setItem('refreshToken', refresh)
+      localStorage.setItem('username', username)
     },
 
     destroyToken (state) {
       state.accessToken = null
       state.refreshToken = null
+      state.username = null
 
       localStorage.removeItem('accessToken')
       localStorage.removeItem('refreshToken')
+      localStorage.removeItem('username')
     },
 
     initFromLocalstore(state) {
-      console.log("Initializing from local store")
-      console.log(localStorage)
+      console.log("Getting auth tokens from local store")
       state.accessToken = localStorage.getItem('accessToken')
       state.refreshToken = localStorage.getItem('refreshToken')
+      state.username = localStorage.getItem('username')
     }
   },
 
@@ -59,7 +65,8 @@ export default new Vuex.Store({
             context.commit('updateStorage',
               {
                 access: response.data.access,
-                refresh: response.data.refresh
+                refresh: response.data.refresh,
+                username: usercredentials.username
               }
             )
             resolve()

+ 1 - 3
frontend/src/views/Index.vue

@@ -1,9 +1,7 @@
 <template>
   <div>
     <h1>Index Page</h1>
-    <p v-if="accessToken!=null">
-      <router-link :to = "{ name:'logout' }">Logout</router-link>
-    </p>
+
   </div>
 </template>
 

+ 78 - 2
frontend/src/views/Login.vue

@@ -1,4 +1,57 @@
 <template>
+  <v-card shaped outlined elevation="2" max-width=600px class="mx-auto">
+    <v-card-title>Login</v-card-title>
+    <v-card-text>
+      <v-form
+        @submit.prevent="login"
+        v-model="valid"
+        ref="form"
+        >
+        <v-container fluid>
+          <v-row>
+            <v-col cols=12 sm=12>
+              <v-text-field
+                v-model="username"
+                name="username"
+                label="Username"
+                :rules="rules.required"
+                required
+              ></v-text-field>
+            </v-col>
+          </v-row>
+          <v-row>
+            <v-col cols=12 sm=12>
+              <v-text-field
+                v-model="password"
+                :append-icon="showPwd ? 'mdi-eye' : 'mdi-eye-off'"
+                name="password"
+                label="Password"
+                :rules="rules.required"
+                :type="showPwd ? 'text' : 'password'"
+                :error-messages="loginErrors"
+                @click:append="showPwd = !showPwd"
+                required
+              ></v-text-field>
+            </v-col>
+          </v-row>
+          <v-row>
+            <v-col cols=12 sm=12>
+              <v-btn
+                class="mr-4"
+                type="submit"
+              >
+                Login
+              </v-btn>
+            </v-col>
+          </v-row>
+
+        </v-container>
+
+      </v-form>
+
+    </v-card-text>
+  </v-card>
+<!--
   <div>
     <p v-if="incorrectAuth">Incorrect username or password entered - please try again</p>
     <form v-on:submit.prevent="login">
@@ -7,18 +60,41 @@
       <button type="submit">Login</button>
     </form>
 
-  </div>
+  </div> -->
 
 </template>
 
 <script>
+  import { mapState } from 'vuex'
   export default {
     name: 'login',
+
+    computed: Object.assign(
+      mapState(["accessToken"]),
+      {
+        loginErrors () {
+          const errors = []
+          this.incorrectAuth && errors.push('Username or password are incorrect.')
+          return errors
+        }
+      }
+    ),
+    created() {
+      if (this.accessToken != null){
+        this.$router.push({ name: 'index' })
+      }
+    },
+
     data () {
       return {
+        valid: false,
         username: '',
         password: '',
-        incorrectAuth: false
+        incorrectAuth: false,
+        showPwd: false,
+        rules: {
+          required: [value => !!value || 'Required.'],
+        },
       }
     },
     methods: {

+ 16 - 0
frontend/src/views/Projects.vue

@@ -0,0 +1,16 @@
+<template>
+  <div>
+    <h1>Projects</h1>
+
+  </div>
+</template>
+
+<script>
+  import { mapState } from 'vuex'
+  export default {
+    name: 'Projects',
+    computed: mapState(["accessToken"])
+  }
+</script>
+
+<style scoped></style>

+ 5 - 0
frontend/vue.config.js

@@ -0,0 +1,5 @@
+module.exports = {
+  transpileDependencies: [
+    'vuetify'
+  ]
+}