├── .browserslistrc ├── src ├── types.ts ├── constants.ts ├── assets │ └── logo.png ├── store │ ├── actions.ts │ └── index.ts ├── views │ ├── About.vue │ └── Home.vue ├── shims-vue.d.ts ├── router │ └── index.ts ├── components │ └── ActiveApps.vue └── main.ts ├── vue.config.js ├── cypress.json ├── public ├── favicon.ico └── index.html ├── babel.config.js ├── jest.config.js ├── tests ├── e2e │ ├── .eslintrc.js │ ├── specs │ │ └── test.js │ ├── support │ │ ├── index.js │ │ └── commands.js │ └── plugins │ │ └── index.js └── unit │ └── example.spec.ts ├── .gitignore ├── README.md ├── .eslintrc.js ├── tsconfig.json └── package.json /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export type Dictionary = { [key: string]: T }; 2 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | lintOnSave: false, 3 | }; 4 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export const ADMIN_WS_URL = "ws://localhost:8889"; 2 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginsFile": "tests/e2e/plugins/index.js" 3 | } 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holochain/admin-ui/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holochain/admin-ui/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@vue/cli-plugin-babel/preset"], 3 | }; 4 | -------------------------------------------------------------------------------- /src/store/actions.ts: -------------------------------------------------------------------------------- 1 | export const Actions = { 2 | fetchActiveHapps: "fetchActiveHapps", 3 | }; 4 | -------------------------------------------------------------------------------- /src/views/About.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: "@vue/cli-plugin-unit-jest/presets/typescript-and-babel", 3 | transform: { 4 | "^.+\\.vue$": "vue-jest", 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /tests/e2e/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ["cypress"], 3 | env: { 4 | mocha: true, 5 | "cypress/globals": true, 6 | }, 7 | rules: { 8 | strict: "off", 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /tests/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // https://docs.cypress.io/api/introduction/api.html 2 | 3 | describe("My First Test", () => { 4 | it("Visits the app root url", () => { 5 | cy.visit("/"); 6 | cy.contains("h1", "Welcome to Your Vue.js + TypeScript App"); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | /tests/e2e/videos/ 6 | /tests/e2e/screenshots/ 7 | 8 | 9 | # local env files 10 | .env.local 11 | .env.*.local 12 | 13 | # Log files 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | pnpm-debug.log* 18 | 19 | # Editor directories and files 20 | .idea 21 | .vscode 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | -------------------------------------------------------------------------------- /tests/unit/example.spec.ts: -------------------------------------------------------------------------------- 1 | import { shallowMount } from "@vue/test-utils"; 2 | import HelloWorld from "@/components/ActiveApps.vue"; 3 | 4 | describe("HelloWorld.vue", () => { 5 | it("renders props.msg when passed", () => { 6 | const msg = "new message"; 7 | const wrapper = shallowMount(HelloWorld, { 8 | props: { msg }, 9 | }); 10 | expect(wrapper.text()).toMatch(msg); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | declare module '*.vue' { 3 | import type { DefineComponent } from 'vue' 4 | const component: DefineComponent<{}, {}, any> 5 | export default component 6 | } 7 | import { Store } from 'vuex'; 8 | import { HcAdminState } from './store'; 9 | 10 | declare module '@vue/runtime-core' { 11 | interface ComponentCustomProperties { 12 | $store: Store<{admin: HcAdminState}>; 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # admin-ui 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Run your unit tests 19 | ``` 20 | npm run test:unit 21 | ``` 22 | 23 | ### Run your end-to-end tests 24 | ``` 25 | npm run test:e2e 26 | ``` 27 | 28 | ### Lints and fixes files 29 | ``` 30 | npm run lint 31 | ``` 32 | 33 | ### Customize configuration 34 | See [Configuration Reference](https://cli.vuejs.org/config/). 35 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router"; 2 | import Home from "../views/Home.vue"; 3 | 4 | const routes: Array = [ 5 | { 6 | path: "/", 7 | name: "Home", 8 | component: Home, 9 | }, 10 | { 11 | path: "/about", 12 | name: "About", 13 | // route level code-splitting 14 | // this generates a separate chunk (about.[hash].js) for this route 15 | // which is lazy-loaded when the route is visited. 16 | component: () => 17 | import(/* webpackChunkName: "about" */ "../views/About.vue"), 18 | }, 19 | ]; 20 | 21 | const router = createRouter({ 22 | history: createWebHashHistory(), 23 | routes, 24 | }); 25 | 26 | export default router; 27 | -------------------------------------------------------------------------------- /tests/e2e/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import "./commands"; 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true, 5 | }, 6 | extends: [ 7 | "plugin:vue/vue3-essential", 8 | "eslint:recommended", 9 | "@vue/typescript/recommended", 10 | "@vue/prettier", 11 | "@vue/prettier/@typescript-eslint", 12 | ], 13 | parserOptions: { 14 | ecmaVersion: 2020, 15 | }, 16 | rules: { 17 | "no-console": process.env.NODE_ENV === "production" ? "warn" : "off", 18 | "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off", 19 | }, 20 | overrides: [ 21 | { 22 | files: [ 23 | "**/__tests__/*.{j,t}s?(x)", 24 | "**/tests/unit/**/*.spec.{j,t}s?(x)", 25 | ], 26 | env: { 27 | jest: true, 28 | }, 29 | }, 30 | ], 31 | }; 32 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "skipLibCheck": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "baseUrl": ".", 14 | "types": [ 15 | "webpack-env", 16 | "jest" 17 | ], 18 | "paths": { 19 | "@/*": [ 20 | "src/*" 21 | ] 22 | }, 23 | "lib": [ 24 | "esnext", 25 | "dom", 26 | "dom.iterable", 27 | "scripthost" 28 | ] 29 | }, 30 | "include": [ 31 | "src/**/*.ts", 32 | "src/**/*.tsx", 33 | "src/**/*.vue", 34 | "tests/**/*.ts", 35 | "tests/**/*.tsx" 36 | ], 37 | "exclude": [ 38 | "node_modules" 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /tests/e2e/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This is will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /src/components/ActiveApps.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 24 | 25 | 26 | 42 | -------------------------------------------------------------------------------- /tests/e2e/plugins/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable arrow-body-style */ 2 | // https://docs.cypress.io/guides/guides/plugins-guide.html 3 | 4 | // if you need a custom webpack configuration you can uncomment the following import 5 | // and then use the `file:preprocessor` event 6 | // as explained in the cypress docs 7 | // https://docs.cypress.io/api/plugins/preprocessors-api.html#Examples 8 | 9 | // /* eslint-disable import/no-extraneous-dependencies, global-require */ 10 | // const webpack = require('@cypress/webpack-preprocessor') 11 | 12 | module.exports = (on, config) => { 13 | // on('file:preprocessor', webpack({ 14 | // webpackOptions: require('@vue/cli-service/webpack.config'), 15 | // watchOptions: {} 16 | // })) 17 | 18 | return Object.assign({}, config, { 19 | fixturesFolder: "tests/e2e/fixtures", 20 | integrationFolder: "tests/e2e/specs", 21 | screenshotsFolder: "tests/e2e/screenshots", 22 | videosFolder: "tests/e2e/videos", 23 | supportFile: "tests/e2e/support/index.js", 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { AdminWebsocket, AppWebsocket } from "@holochain/conductor-api"; 2 | import { App, createApp } from "vue"; 3 | import { Store } from "vuex"; 4 | import ActiveApps from "@/components/ActiveApps.vue"; // @ is an alias to /src 5 | import { hcAdminVuexModule } from "./store"; 6 | 7 | //createApp(App).use(store).use(router).mount("#app"); 8 | 9 | export default { 10 | install( 11 | app: App, 12 | options: { 13 | appWebsocket: AppWebsocket; 14 | adminWebsocket: AdminWebsocket; 15 | store: Store; 16 | } 17 | ) { 18 | if (!options.adminWebsocket) 19 | throw new Error( 20 | `Failed to load the plugin: no "adminWebsocket" was provided in the plugin options` 21 | ); 22 | if (!options.appWebsocket) 23 | throw new Error( 24 | `Failed to load the plugin: no "appWebsocket" was provided in the plugin options` 25 | ); 26 | if (!options.store) 27 | throw new Error( 28 | `Failed to load the plugin: no Vuex "store" was provided in the plugin options` 29 | ); 30 | 31 | options.store.registerModule( 32 | "admin", 33 | hcAdminVuexModule(options.adminWebsocket, options.appWebsocket) 34 | ); 35 | 36 | app.component("ActiveApps", ActiveApps); 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { createStore, Module } from "vuex"; 2 | import { 3 | AdminWebsocket, 4 | AppWebsocket, 5 | InstalledAppInfo, 6 | } from "@holochain/conductor-api"; 7 | import { Dictionary } from "@/types"; 8 | 9 | export interface HcAdminState { 10 | activeApps: { loading: boolean; appsInfo: Dictionary }; 11 | } 12 | 13 | export function hcAdminVuexModule( 14 | adminWebsocket: AdminWebsocket, 15 | appWebsocket: AppWebsocket 16 | ): Module { 17 | return { 18 | state() { 19 | return { 20 | activeApps: { 21 | loading: false, 22 | appsInfo: {}, 23 | }, 24 | }; 25 | }, 26 | mutations: { 27 | loadAppsInfo(state) { 28 | state.activeApps.loading = true; 29 | }, 30 | setAppsInfo(state, activeApps) { 31 | state.activeApps.appsInfo = activeApps; 32 | state.activeApps.loading = false; 33 | }, 34 | }, 35 | actions: { 36 | async fetchActiveHapps({ commit }) { 37 | commit("loadAppsInfo"); 38 | const activeAppsIds = await adminWebsocket.listActiveApps(); 39 | 40 | const promises = activeAppsIds.map((appId) => 41 | appWebsocket.appInfo({ installed_app_id: appId }) 42 | ); 43 | 44 | const activeApps = await Promise.all(promises); 45 | 46 | const apps: Dictionary = {}; 47 | 48 | for (const app of activeApps) { 49 | apps[app.installed_app_id] = app; 50 | } 51 | 52 | commit("setAppsInfo", apps); 53 | }, 54 | }, 55 | }; 56 | } 57 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "admin-ui", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build --target lib --name hc-admin-ui ./src/main.ts", 8 | "test:unit": "vue-cli-service test:unit", 9 | "test:e2e": "vue-cli-service test:e2e", 10 | "lint": "vue-cli-service lint", 11 | "publish-to-branch": "npm run build && gh-pages -d dist -b build" 12 | }, 13 | "dependencies": { 14 | "@holochain/conductor-api": "0.0.4", 15 | "core-js": "^3.6.5", 16 | "vue": "^3.0.0", 17 | "vue-router": "^4.0.0-0", 18 | "vuex": "^4.0.0-0" 19 | }, 20 | "devDependencies": { 21 | "@types/jest": "^24.0.19", 22 | "@typescript-eslint/eslint-plugin": "^4.18.0", 23 | "@typescript-eslint/parser": "^4.18.0", 24 | "@vue/cli-plugin-babel": "~4.5.0", 25 | "@vue/cli-plugin-e2e-cypress": "~4.5.0", 26 | "@vue/cli-plugin-eslint": "~4.5.0", 27 | "@vue/cli-plugin-router": "~4.5.0", 28 | "@vue/cli-plugin-typescript": "~4.5.0", 29 | "@vue/cli-plugin-unit-jest": "~4.5.0", 30 | "@vue/cli-plugin-vuex": "~4.5.0", 31 | "@vue/cli-service": "~4.5.0", 32 | "@vue/compiler-sfc": "^3.0.0", 33 | "@vue/eslint-config-prettier": "^6.0.0", 34 | "@vue/eslint-config-typescript": "^7.0.0", 35 | "@vue/test-utils": "^2.0.0-0", 36 | "eslint": "^6.7.2", 37 | "eslint-plugin-prettier": "^3.3.1", 38 | "eslint-plugin-vue": "^7.0.0", 39 | "gh-pages": "^3.2.0", 40 | "lint-staged": "^9.5.0", 41 | "prettier": "^2.2.1", 42 | "typescript": "~4.1.5", 43 | "vue-jest": "^5.0.0-0" 44 | }, 45 | "gitHooks": { 46 | "pre-commit": "lint-staged" 47 | }, 48 | "lint-staged": { 49 | "*.{js,jsx,vue,ts,tsx}": [ 50 | "vue-cli-service lint", 51 | "git add" 52 | ] 53 | } 54 | } 55 | --------------------------------------------------------------------------------