6
0
Эх сурвалжийг харах

reworked scripts in different modules

Dimitri Korsch 3 жил өмнө
parent
commit
17680edac1

+ 92 - 81
frontend/package-lock.json

@@ -1761,6 +1761,63 @@
           "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
           "dev": true
         },
+        "ansi-styles": {
+          "version": "4.3.0",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+          "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "color-convert": "^2.0.1"
+          }
+        },
+        "chalk": {
+          "version": "4.1.2",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+          "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "ansi-styles": "^4.1.0",
+            "supports-color": "^7.1.0"
+          }
+        },
+        "color-convert": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "color-name": "~1.1.4"
+          }
+        },
+        "color-name": {
+          "version": "1.1.4",
+          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+          "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+          "dev": true,
+          "optional": true
+        },
+        "has-flag": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+          "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+          "dev": true,
+          "optional": true
+        },
+        "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,
+          "optional": true,
+          "requires": {
+            "big.js": "^5.2.2",
+            "emojis-list": "^3.0.0",
+            "json5": "^2.1.2"
+          }
+        },
         "ssri": {
           "version": "8.0.1",
           "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz",
@@ -1769,6 +1826,28 @@
           "requires": {
             "minipass": "^3.1.1"
           }
+        },
+        "supports-color": {
+          "version": "7.2.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+          "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "has-flag": "^4.0.0"
+          }
+        },
+        "vue-loader-v16": {
+          "version": "npm:vue-loader@16.8.3",
+          "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.8.3.tgz",
+          "integrity": "sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA==",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "chalk": "^4.1.0",
+            "hash-sum": "^2.0.0",
+            "loader-utils": "^2.0.0"
+          }
         }
       }
     },
@@ -6124,6 +6203,19 @@
         "postcss": "^7.0.14"
       }
     },
+    "idle-js": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/idle-js/-/idle-js-0.1.3.tgz",
+      "integrity": "sha512-Hvd+OLMUWfVJUv/HoX7wtBgPwx2OdcEd2TzXcBQJyuINXcf7Ig2SgxRMuq2wZNkarf/+DrzV7NxwjKcexIfWBg=="
+    },
+    "idle-vue": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/idle-vue/-/idle-vue-2.0.5.tgz",
+      "integrity": "sha1-MA48zZWBcMTX76fMmuzoYHi+9XY=",
+      "requires": {
+        "idle-js": "^0.1.3"
+      }
+    },
     "ieee754": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@@ -11248,87 +11340,6 @@
         }
       }
     },
-    "vue-loader-v16": {
-      "version": "npm:vue-loader@16.8.3",
-      "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.8.3.tgz",
-      "integrity": "sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA==",
-      "dev": true,
-      "optional": true,
-      "requires": {
-        "chalk": "^4.1.0",
-        "hash-sum": "^2.0.0",
-        "loader-utils": "^2.0.0"
-      },
-      "dependencies": {
-        "ansi-styles": {
-          "version": "4.3.0",
-          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
-          "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "color-convert": "^2.0.1"
-          }
-        },
-        "chalk": {
-          "version": "4.1.2",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
-          "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "ansi-styles": "^4.1.0",
-            "supports-color": "^7.1.0"
-          }
-        },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "color-name": "~1.1.4"
-          }
-        },
-        "color-name": {
-          "version": "1.1.4",
-          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
-          "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
-          "dev": true,
-          "optional": true
-        },
-        "has-flag": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
-          "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-          "dev": true,
-          "optional": true
-        },
-        "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,
-          "optional": true,
-          "requires": {
-            "big.js": "^5.2.2",
-            "emojis-list": "^3.0.0",
-            "json5": "^2.1.2"
-          }
-        },
-        "supports-color": {
-          "version": "7.2.0",
-          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
-          "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "has-flag": "^4.0.0"
-          }
-        }
-      }
-    },
     "vue-router": {
       "version": "3.5.3",
       "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.5.3.tgz",

+ 1 - 0
frontend/package.json

@@ -10,6 +10,7 @@
   "dependencies": {
     "axios": "^0.24.0",
     "core-js": "^3.6.5",
+    "idle-vue": "^2.0.5",
     "vue": "^2.6.11",
     "vue-router": "^3.5.3",
     "vuetify": "^2.4.0",

+ 0 - 8
frontend/src/axios-api.js

@@ -1,8 +0,0 @@
-import axios from 'axios'
-
-const getAPI = axios.create({
-    baseURL: 'http://127.0.0.1:5000',
-    timeout: 1000,
-})
-
-export { getAPI }

+ 11 - 4
frontend/src/components/Drawer.vue

@@ -19,7 +19,7 @@
       <div class="row">
         <div class="col-md-6">{{ username }}</div>
         <div class="col-md-3">
-          <div v-if="accessToken != null">
+          <div v-if="loggedIn">
             <v-btn :to = "{ name: 'logout' }" small primary>
               <span>Logout</span>
               <v-icon>mdi-logout</v-icon>
@@ -31,7 +31,7 @@
 
     <v-divider></v-divider>
 
-    <v-list v-if="accessToken != null" >
+    <v-list v-if="loggedIn" >
       <v-list-item
         v-for="[icon, text, dest] in links"
         :key="icon"
@@ -51,10 +51,17 @@
 </template>
 
 <script>
-import { mapState } from 'vuex'
+
 export default {
   name: "Drawer",
-  computed: mapState(["accessToken", "username"]),
+  computed: {
+    loggedIn() {
+      return this.$store.state.auth.loggedIn;
+    },
+    username() {
+      return this.$store.state.auth.tokens?.username
+    }
+  },
   data: () => ({
     links: [
       [

+ 7 - 10
frontend/src/main.js

@@ -3,24 +3,21 @@ import App from './App.vue'
 import router from './routes'
 import store from './store'
 import vuetify from './plugins/vuetify'
+import setupInterceptors from './services/setupInterceptors';
+import Vuex from 'vuex';
 
 Vue.config.productionTip = false
 
 
-store.commit('initFromLocalstore');
-
 router.beforeEach((to, from, next) => {
-  if (to.matched.some(record => record.meta.requiresLogin)) {
-    if (!store.getters.loggedIn) {
+  if (to.matched.some(record => record.meta.requiresLogin))
+    if (!store.state.auth.loggedIn)
       next({ name: 'login' })
-    } else {
-      next()
-    }
-  } else {
-    next()
-  }
+  next()
 })
 
+Vue.use(Vuex);
+setupInterceptors(store);
 
 new Vue({
   router,

+ 11 - 0
frontend/src/services/api.js

@@ -0,0 +1,11 @@
+import axios from 'axios'
+
+const instance = axios.create({
+  baseURL: 'http://127.0.0.1:5000',
+  timeout: 1000,
+  headers: {
+    "Content-Type": "application/json",
+  },
+})
+
+export default instance;

+ 23 - 0
frontend/src/services/auth.service.js

@@ -0,0 +1,23 @@
+import api from "./api";
+import TokenService from "./token.service";
+
+class AuthService {
+  login({ username, password }) {
+    return api
+      .post("/api-token/", {username, password})
+      .then((response) => {
+        if (response.data.access) {
+          TokenService.setTokens(username, response.data);
+        }
+
+        return response.data;
+      });
+  }
+
+  logout() {
+    TokenService.removeTokens();
+  }
+
+}
+
+export default new AuthService();

+ 14 - 0
frontend/src/services/data.service.js

@@ -0,0 +1,14 @@
+import api from "./api";
+
+class DataService {
+
+  getModels() {
+    return api.get("/models/").then(
+      (response) => {
+        return response.data;
+      });
+  }
+}
+
+
+export default new DataService();

+ 55 - 0
frontend/src/services/setupInterceptors.js

@@ -0,0 +1,55 @@
+import axiosInstance from "./api";
+import TokenService from "./token.service";
+
+const setup = (store) => {
+  axiosInstance.interceptors.request.use(
+    (config) => {
+      const token = TokenService.getLocalAccessToken();
+      if (token) {
+        config.headers["Authorization"] = `Bearer ${token}`
+      }
+      return config;
+    },
+    (error) => {
+      console.log(error);
+      return Promise.reject(error);
+    }
+  );
+
+  axiosInstance.interceptors.response.use(
+    (res) => {
+      return res;
+    },
+    async (err) => {
+      const originalConfig = err.config;
+      console.log("Access token expired!")
+
+      if (originalConfig.url !== "/api-token/" && err.response) {
+        // Access Token was expired
+        if (err.response.status === 401 && !originalConfig._retry) {
+
+          console.log("Access token expired!")
+          originalConfig._retry = true;
+          try {
+            const response = await axiosInstance.post("/api-token-refresh/", {
+              refresh: TokenService.getLocalRefreshToken(),
+            });
+
+            const { access } = response.data;
+
+            store.dispatch('auth/refreshToken', access);
+            TokenService.updateLocalAccessToken(access);
+
+            return axiosInstance(originalConfig);
+          } catch (_error) {
+            return Promise.reject(_error);
+          }
+        }
+      }
+
+      return Promise.reject(err);
+    }
+  );
+};
+
+export default setup;

+ 39 - 0
frontend/src/services/token.service.js

@@ -0,0 +1,39 @@
+class TokenService {
+
+  getTokens() {
+    return JSON.parse(localStorage.getItem("tokens"));
+  }
+
+  setTokens(username, tokens) {
+    let tokens_str = JSON.stringify(
+      Object.assign(
+        tokens, {username}
+      )
+    )
+    // console.log("Setting token info: ", tokens_str);
+    localStorage.setItem("tokens", tokens_str);
+  }
+
+  removeTokens() {
+    localStorage.removeItem("tokens");
+  }
+
+  getLocalRefreshToken() {
+    const tokens = this.getTokens();
+    return tokens?.refresh;
+  }
+
+  getLocalAccessToken() {
+    const tokens = this.getTokens();
+    return tokens?.access;
+  }
+
+  updateLocalAccessToken(token) {
+    let tokens = this.getTokens();
+    tokens.access = token;
+    this.setTokens(tokens);
+  }
+
+}
+
+export default new TokenService();

+ 0 - 82
frontend/src/store.js

@@ -1,82 +0,0 @@
-import Vue from 'vue'
-import Vuex from 'vuex'
-
-import { getAPI } from './axios-api'
-
-Vue.use(Vuex)
-export default new Vuex.Store({
-  state: {
-     accessToken: null,
-     refreshToken: null,
-
-     username: null,
-     models: null,
-  },
-
-  mutations: {
-    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("Getting auth tokens from local store")
-      state.accessToken = localStorage.getItem('accessToken')
-      state.refreshToken = localStorage.getItem('refreshToken')
-      state.username = localStorage.getItem('username')
-    }
-  },
-
-  getters: {
-    loggedIn (state) {
-      return state.accessToken != null
-    }
-  },
-
-  actions: {
-
-    userLogout (context) {
-      if (context.getters.loggedIn) {
-          context.commit('destroyToken')
-      }
-    },
-
-    userLogin (context, usercredentials) {
-      return new Promise((resolve, reject) => {
-        getAPI.post('/api-token/', {
-          username: usercredentials.username,
-          password: usercredentials.password
-        })
-          .then(response => {
-            context.commit('updateStorage',
-              {
-                access: response.data.access,
-                refresh: response.data.refresh,
-                username: usercredentials.username
-              }
-            )
-            resolve()
-          })
-          .catch(err => {
-            reject(err)
-          })
-      })
-    }
-
-  }
-})

+ 51 - 0
frontend/src/store/auth.module.js

@@ -0,0 +1,51 @@
+import AuthService from '../services/auth.service';
+import TokenService from '../services/token.service';
+
+const tokens = TokenService.getTokens();
+const initialState = tokens
+  ? { loggedIn: true , tokens }
+  : { loggedIn: false , tokens: null };
+
+export const auth = {
+  namespaced: true,
+  state: initialState,
+  actions: {
+    login({ commit }, user) {
+      return AuthService.login(user).then(
+        tokens => {
+          commit('loginSuccess', tokens);
+          return Promise.resolve(tokens);
+        },
+        error => {
+          commit('loginFailure');
+          return Promise.reject(error);
+        }
+      );
+    },
+    logout({ commit }) {
+      AuthService.logout();
+      commit('logout');
+    },
+    refreshToken({ commit }, access) {
+      commit('refreshToken', access);
+    }
+  },
+  mutations: {
+    loginSuccess(state, tokens) {
+      state.loggedIn = true;
+      state.tokens = tokens;
+    },
+    loginFailure(state) {
+      state.loggedIn = false;
+      state.tokens = null;
+    },
+    logout(state) {
+      state.loggedIn = false;
+      state.tokens = null;
+    },
+    refreshToken(state, access) {
+      state.loggedIn = true;
+      state.tokens = { ...state.tokens, access: access };
+    }
+  }
+};

+ 24 - 0
frontend/src/store/data.module.js

@@ -0,0 +1,24 @@
+import DataService from '../services/data.service';
+
+export const data = {
+
+  namespaced: true,
+  state: {
+    models: []
+  },
+
+  actions : {
+    getModels({ commit }) {
+      DataService.getModels().then(
+        (models) => {
+          commit("setModels", models)
+        })
+    }
+  },
+
+  mutations : {
+    setModels(state, models) {
+      state.models = models;
+    }
+  },
+}

+ 13 - 0
frontend/src/store/index.js

@@ -0,0 +1,13 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+
+import { auth } from './auth.module';
+import { data } from './data.module';
+
+Vue.use(Vuex);
+
+export default new Vuex.Store({
+  modules: {
+    auth, data
+  }
+});

+ 6 - 0
frontend/src/store/user.js

@@ -0,0 +1,6 @@
+export default class User {
+  constructor(username, password) {
+    this.username = username;
+    this.password = password;
+  }
+}

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

@@ -1,15 +1,12 @@
 <template>
   <div>
     <h1>Index Page</h1>
-
   </div>
 </template>
 
 <script>
-  import { mapState } from 'vuex'
   export default {
     name: 'Index',
-    computed: mapState(["accessToken"])
   }
 </script>
 

+ 35 - 43
frontend/src/views/Login.vue

@@ -11,7 +11,7 @@
           <v-row>
             <v-col cols=12 sm=12>
               <v-text-field
-                v-model="username"
+                v-model="user.username"
                 name="username"
                 label="Username"
                 :rules="rules.required"
@@ -22,7 +22,7 @@
           <v-row>
             <v-col cols=12 sm=12>
               <v-text-field
-                v-model="password"
+                v-model="user.password"
                 :append-icon="showPwd ? 'mdi-eye' : 'mdi-eye-off'"
                 name="password"
                 label="Password"
@@ -51,45 +51,19 @@
 
     </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">
-      <input type="text" name="username" id="user" v-model="username" placeholder="Username">
-      <input type="password" name="password" id="pass" v-model="password" placeholder="Password">
-      <button type="submit">Login</button>
-    </form>
-
-  </div> -->
-
 </template>
 
 <script>
-  import { mapState } from 'vuex'
+  import User from '@/store/user';
+
   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: '',
+        user: new User('', ''),
+        loading: false,
         incorrectAuth: false,
         showPwd: false,
         rules: {
@@ -97,19 +71,37 @@
         },
       }
     },
+
+    computed: {
+      loginErrors () {
+        const errors = []
+        this.incorrectAuth && errors.push('Username or password are incorrect.')
+        return errors
+      },
+      loggedIn() {
+        return this.$store.state.auth.loggedIn;
+      }
+    },
+    created() {
+      if (this.loggedIn){
+        this.$router.push({ name: 'index' })
+      }
+    },
+
+
     methods: {
       login () {
-        this.$store.dispatch('userLogin', {
-          username: this.username,
-          password: this.password
-        })
-        .then(() => {
-          this.$router.push({ name: 'index' })
-        })
-        .catch(err => {
-          console.log(err)
-          this.incorrectAuth = true
-        })
+        this.loading = true;
+
+        this.$store.dispatch('auth/login', this.user).then(
+          () => {
+            this.$router.push({ name: 'index' })
+          },
+          err => {
+            console.log(err)
+            this.incorrectAuth = true
+          }
+        )
       }
     }
   }

+ 1 - 2
frontend/src/views/Logout.vue

@@ -6,8 +6,7 @@
 <script>
   export default {
     created () {
-      console.log("Logged out")
-      this.$store.dispatch('userLogout')
+      this.$store.dispatch('auth/logout')
         .then(() => {
           this.$router.push({ name: 'login' })
         })

+ 4 - 12
frontend/src/views/Projects.vue

@@ -12,7 +12,7 @@
     </v-row>
     <v-row dense>
       <v-col
-        v-for="model in models"
+        v-for="model in data.models"
         :key="model.id"
         :cols=4
       >
@@ -34,22 +34,14 @@
 </template>
 
 <script>
+  // import api from '@/services/api'
   import { mapState } from 'vuex'
-  import { getAPI } from '@/axios-api'
 
   export default {
     name: 'Projects',
-    computed: mapState(["models"]),
-
+    computed: mapState(['data']),
     created () {
-      getAPI.get('/models/',
-        { headers: { Authorization: `Bearer ${this.$store.state.accessToken}`}})
-        .then(response => {
-          this.$store.state.models = response.data
-        })
-        .catch(err => {
-          console.log(err)
-        })
+      this.$store.dispatch('data/getModels')
     },
     methods: {
       addProject() {