├── .editorconfig ├── .gitignore ├── README.md ├── assets ├── README.md ├── css │ └── main.css └── js │ ├── data.js │ ├── script.js │ └── scriptplus.js ├── components ├── Client.vue ├── Logo.vue ├── README.md └── lang-switcher.vue ├── config ├── get-cookie.js ├── i18n.js └── lang │ ├── en.js │ └── zh.js ├── layouts ├── README.md └── default.vue ├── middleware ├── README.md └── lang.js ├── nuxt.config.js ├── package-lock.json ├── package.json ├── pages ├── README.md ├── home.vue ├── index.vue ├── test.vue ├── testcar.vue └── testmnist.vue ├── plugins ├── README.md └── element-ui.js ├── static ├── README.md ├── favicon.ico ├── github.png └── mnist │ ├── mnist_images.png │ └── mnist_labels_uint8 └── store ├── README.md ├── index.js └── lang.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | /logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # TypeScript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | 62 | # parcel-bundler cache (https://parceljs.org/) 63 | .cache 64 | 65 | # next.js build output 66 | .next 67 | 68 | # nuxt.js build output 69 | .nuxt 70 | 71 | # Nuxt generate 72 | dist 73 | 74 | # vuepress build output 75 | .vuepress/dist 76 | 77 | # Serverless directories 78 | .serverless 79 | 80 | # IDE / Editor 81 | .idea 82 | 83 | # Service worker 84 | sw.* 85 | 86 | # macOS 87 | .DS_Store 88 | 89 | # Vim swap files 90 | *.swp 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Federated Learning Blockchain Web Demo 2 | 3 | *** 4 | 5 | Preview Online: http://demo.blockchain-neu.com 6 | 7 | *** 8 | 9 | [Vue.js](https://cn.vuejs.org/) + [Vue CLI](https://cli.vuejs.org/) + [Vue Router](https://router.vuejs.org/zh/) + [Vuex](https://vuex.vuejs.org/) + [Nuxt.js](https://nuxtjs.org/) + [Element UI](https://element.eleme.cn/#/zh-CN/component/installation) 10 | 11 | *** 12 | 13 | ## Build Setup 14 | 15 | ``` bash 16 | # install dependencies 17 | $ npm install 18 | 19 | # serve with hot reload at localhost:3000 20 | $ npm run dev 21 | 22 | # build for production and launch server 23 | $ npm run build 24 | $ npm run start 25 | 26 | # generate static project 27 | $ npm run generate 28 | ``` 29 | 30 | For detailed explanation on how things work, check out [Nuxt.js docs](https://nuxtjs.org). 31 | -------------------------------------------------------------------------------- /assets/README.md: -------------------------------------------------------------------------------- 1 | # ASSETS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your un-compiled assets such as LESS, SASS, or JavaScript. 6 | 7 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#webpacked). 8 | -------------------------------------------------------------------------------- /assets/css/main.css: -------------------------------------------------------------------------------- 1 | /*.page-enter-active, .page-leave-active {*/ 2 | /* transition: opacity .0s;*/ 3 | /*}*/ 4 | /*.page-enter, .page-leave-active {*/ 5 | /* opacity: 0;*/ 6 | /*}*/ 7 | -------------------------------------------------------------------------------- /assets/js/data.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2018 Google LLC. All Rights Reserved. 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * ============================================================================= 16 | */ 17 | 18 | const IMAGE_SIZE = 784; 19 | const NUM_CLASSES = 10; 20 | const NUM_DATASET_ELEMENTS = 65000; 21 | 22 | const NUM_TRAIN_ELEMENTS = 55000; 23 | const NUM_TEST_ELEMENTS = NUM_DATASET_ELEMENTS - NUM_TRAIN_ELEMENTS; 24 | 25 | const MNIST_IMAGES_SPRITE_PATH = 26 | '/mnist/mnist_images.png'; 27 | const MNIST_LABELS_PATH = 28 | '/mnist/mnist_labels_uint8'; 29 | 30 | import * as tf from '@tensorflow/tfjs'; 31 | 32 | /** 33 | * A class that fetches the sprited MNIST dataset and returns shuffled batches. 34 | * 35 | * NOTE: This will get much easier. For now, we do data fetching and 36 | * manipulation manually. 37 | */ 38 | export class MnistData { 39 | constructor() { 40 | this.shuffledTrainIndex = 0; 41 | this.shuffledTestIndex = 0; 42 | } 43 | 44 | async load() { 45 | // Make a request for the MNIST sprited image. 46 | const img = new Image(); 47 | const canvas = document.createElement('canvas'); 48 | const ctx = canvas.getContext('2d'); 49 | const imgRequest = new Promise((resolve, reject) => { 50 | img.crossOrigin = ''; 51 | img.onload = () => { 52 | img.width = img.naturalWidth; 53 | img.height = img.naturalHeight; 54 | 55 | const datasetBytesBuffer = 56 | new ArrayBuffer(NUM_DATASET_ELEMENTS * IMAGE_SIZE * 4); 57 | 58 | const chunkSize = 5000; 59 | canvas.width = img.width; 60 | canvas.height = chunkSize; 61 | 62 | for (let i = 0; i < NUM_DATASET_ELEMENTS / chunkSize; i++) { 63 | const datasetBytesView = new Float32Array( 64 | datasetBytesBuffer, i * IMAGE_SIZE * chunkSize * 4, 65 | IMAGE_SIZE * chunkSize); 66 | ctx.drawImage( 67 | img, 0, i * chunkSize, img.width, chunkSize, 0, 0, img.width, 68 | chunkSize); 69 | 70 | const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); 71 | 72 | for (let j = 0; j < imageData.data.length / 4; j++) { 73 | // All channels hold an equal value since the image is grayscale, so 74 | // just read the red channel. 75 | datasetBytesView[j] = imageData.data[j * 4] / 255; 76 | } 77 | } 78 | this.datasetImages = new Float32Array(datasetBytesBuffer); 79 | 80 | resolve(); 81 | }; 82 | img.src = MNIST_IMAGES_SPRITE_PATH; 83 | }); 84 | 85 | const labelsRequest = fetch(MNIST_LABELS_PATH); 86 | const [imgResponse, labelsResponse] = 87 | await Promise.all([imgRequest, labelsRequest]); 88 | 89 | this.datasetLabels = new Uint8Array(await labelsResponse.arrayBuffer()); 90 | 91 | // Create shuffled indices into the train/test set for when we select a 92 | // random dataset element for training / validation. 93 | this.trainIndices = tf.util.createShuffledIndices(NUM_TRAIN_ELEMENTS); 94 | this.testIndices = tf.util.createShuffledIndices(NUM_TEST_ELEMENTS); 95 | 96 | // Slice the the images and labels into train and test sets. 97 | this.trainImages = 98 | this.datasetImages.slice(0, IMAGE_SIZE * NUM_TRAIN_ELEMENTS); 99 | this.testImages = this.datasetImages.slice(IMAGE_SIZE * NUM_TRAIN_ELEMENTS); 100 | this.trainLabels = 101 | this.datasetLabels.slice(0, NUM_CLASSES * NUM_TRAIN_ELEMENTS); 102 | this.testLabels = 103 | this.datasetLabels.slice(NUM_CLASSES * NUM_TRAIN_ELEMENTS); 104 | } 105 | 106 | nextTrainBatch(batchSize) { 107 | return this.nextBatch( 108 | batchSize, [this.trainImages, this.trainLabels], () => { 109 | this.shuffledTrainIndex = 110 | (this.shuffledTrainIndex + 1) % this.trainIndices.length; 111 | return this.trainIndices[this.shuffledTrainIndex]; 112 | }); 113 | } 114 | 115 | nextTestBatch(batchSize) { 116 | return this.nextBatch(batchSize, [this.testImages, this.testLabels], () => { 117 | this.shuffledTestIndex = 118 | (this.shuffledTestIndex + 1) % this.testIndices.length; 119 | return this.testIndices[this.shuffledTestIndex]; 120 | }); 121 | } 122 | 123 | nextBatch(batchSize, data, index) { 124 | const batchImagesArray = new Float32Array(batchSize * IMAGE_SIZE); 125 | const batchLabelsArray = new Uint8Array(batchSize * NUM_CLASSES); 126 | 127 | for (let i = 0; i < batchSize; i++) { 128 | const idx = index(); 129 | 130 | const image = 131 | data[0].slice(idx * IMAGE_SIZE, idx * IMAGE_SIZE + IMAGE_SIZE); 132 | batchImagesArray.set(image, i * IMAGE_SIZE); 133 | 134 | const label = 135 | data[1].slice(idx * NUM_CLASSES, idx * NUM_CLASSES + NUM_CLASSES); 136 | batchLabelsArray.set(label, i * NUM_CLASSES); 137 | } 138 | 139 | const xs = tf.tensor2d(batchImagesArray, [batchSize, IMAGE_SIZE]); 140 | const labels = tf.tensor2d(batchLabelsArray, [batchSize, NUM_CLASSES]); 141 | 142 | return {xs, labels}; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /assets/js/script.js: -------------------------------------------------------------------------------- 1 | import {MnistData} from './data.js'; 2 | import * as tf from '@tensorflow/tfjs'; 3 | 4 | 5 | function getModel() { 6 | const model = tf.sequential(); 7 | 8 | const IMAGE_WIDTH = 28; 9 | const IMAGE_HEIGHT = 28; 10 | const IMAGE_CHANNELS = 1; 11 | 12 | model.add(tf.layers.conv2d({ 13 | inputShape: [IMAGE_WIDTH, IMAGE_HEIGHT, IMAGE_CHANNELS], 14 | kernelSize: 5, 15 | filters: 8, 16 | strides: 1, 17 | activation: 'relu', 18 | kernelInitializer: 'varianceScaling' 19 | })); 20 | 21 | model.add(tf.layers.maxPooling2d({poolSize: [2, 2], strides: [2, 2]})); 22 | 23 | model.add(tf.layers.conv2d({ 24 | kernelSize: 5, 25 | filters: 16, 26 | strides: 1, 27 | activation: 'relu', 28 | kernelInitializer: 'varianceScaling' 29 | })); 30 | model.add(tf.layers.maxPooling2d({poolSize: [2, 2], strides: [2, 2]})); 31 | 32 | model.add(tf.layers.flatten()); 33 | 34 | const NUM_OUTPUT_CLASSES = 10; 35 | model.add(tf.layers.dense({ 36 | units: NUM_OUTPUT_CLASSES, 37 | kernelInitializer: 'varianceScaling', 38 | activation: 'softmax' 39 | })); 40 | 41 | const optimizer = tf.train.adam(); 42 | model.compile({ 43 | optimizer: optimizer, 44 | loss: 'categoricalCrossentropy', 45 | metrics: ['accuracy'], 46 | }); 47 | 48 | return model; 49 | } 50 | 51 | async function train(model, data) { 52 | const BATCH_SIZE = 32; 53 | const TRAIN_DATA_SIZE = 5500; 54 | const TEST_DATA_SIZE = 1000; 55 | 56 | const [trainXs, trainYs] = tf.tidy(() => { 57 | const d = data.nextTrainBatch(TRAIN_DATA_SIZE); 58 | return [ 59 | d.xs.reshape([TRAIN_DATA_SIZE, 28, 28, 1]), 60 | d.labels 61 | ]; 62 | }); 63 | 64 | const [testXs, testYs] = tf.tidy(() => { 65 | const d = data.nextTestBatch(TEST_DATA_SIZE); 66 | return [ 67 | d.xs.reshape([TEST_DATA_SIZE, 28, 28, 1]), 68 | d.labels 69 | ]; 70 | }); 71 | 72 | return model.fit(trainXs, trainYs, { 73 | batchSize: BATCH_SIZE, 74 | validationData: [testXs, testYs], 75 | epochs: 10, 76 | shuffle: true 77 | }); 78 | } 79 | 80 | // async function run() { 81 | // console.log('start run...') 82 | // const data = new MnistData(); 83 | // await data.load(); 84 | // 85 | // const model = getModel(); 86 | // let history = await train(model, data); 87 | // // console.log(history.history.val_loss) 88 | // // console.log(history.history.val_acc) 89 | // // console.log(history.history.loss) 90 | // // console.log(history.history.acc) 91 | // console.log('end run!') 92 | // console.log(typeof history.history.loss) 93 | // console.log(typeof history.history.acc) 94 | // console.log([history.history.loss, history.history.acc]) 95 | // return [history.history.loss, history.history.acc] 96 | // } 97 | 98 | function run() { 99 | let loss = [ 100 | 0.63090158939361572, 101 | 0.51688756585121155, 102 | 0.49303062558174133, 103 | 0.44251964330673218, 104 | 0.36046714872121811, 105 | 0.28329486429691315, 106 | 0.16631346374750137, 107 | 0.10405736297369003, 108 | 0.06184576824307442, 109 | 0.05369441211223602] 110 | let acc = [ 111 | 0.5098181557655334, 112 | 0.5638181805610657, 113 | 0.6107272338867188, 114 | 0.64127272605896, 115 | 0.6938181328773499, 116 | 0.7601818227767944, 117 | 0.8834545350074768, 118 | 0.9278181314468384, 119 | 0.9599999594688416, 120 | 0.9834545254707336] 121 | 122 | return [loss, acc] 123 | 124 | } 125 | 126 | export { 127 | run 128 | } 129 | 130 | -------------------------------------------------------------------------------- /assets/js/scriptplus.js: -------------------------------------------------------------------------------- 1 | import {MnistData} from './data.js'; 2 | import * as tfvis from '@tensorflow/tfjs-vis' 3 | import * as tf from '@tensorflow/tfjs'; 4 | 5 | async function showExamples(data) { 6 | // Create a container in the visor 7 | const surface = 8 | tfvis.visor().surface({name: 'Input Data Examples', tab: 'Input Data'}); 9 | 10 | // Get the examples 11 | const examples = data.nextTestBatch(20); 12 | const numExamples = examples.xs.shape[0]; 13 | 14 | // Create a canvas element to render each example 15 | for (let i = 0; i < numExamples; i++) { 16 | const imageTensor = tf.tidy(() => { 17 | // Reshape the image to 28x28 px 18 | return examples.xs 19 | .slice([i, 0], [1, examples.xs.shape[1]]) 20 | .reshape([28, 28, 1]); 21 | }); 22 | 23 | const canvas = document.createElement('canvas'); 24 | canvas.width = 28; 25 | canvas.height = 28; 26 | canvas.style = 'margin: 4px;'; 27 | await tf.browser.toPixels(imageTensor, canvas); 28 | surface.drawArea.appendChild(canvas); 29 | 30 | imageTensor.dispose(); 31 | } 32 | } 33 | 34 | function getModel() { 35 | const model = tf.sequential(); 36 | 37 | const IMAGE_WIDTH = 28; 38 | const IMAGE_HEIGHT = 28; 39 | const IMAGE_CHANNELS = 1; 40 | 41 | // In the first layer of our convolutional neural network we have 42 | // to specify the input shape. Then we specify some parameters for 43 | // the convolution operation that takes place in this layer. 44 | model.add(tf.layers.conv2d({ 45 | inputShape: [IMAGE_WIDTH, IMAGE_HEIGHT, IMAGE_CHANNELS], 46 | kernelSize: 5, 47 | filters: 8, 48 | strides: 1, 49 | activation: 'relu', 50 | kernelInitializer: 'varianceScaling' 51 | })); 52 | 53 | // The MaxPooling layer acts as a sort of downsampling using max values 54 | // in a region instead of averaging. 55 | model.add(tf.layers.maxPooling2d({poolSize: [2, 2], strides: [2, 2]})); 56 | 57 | // Repeat another conv2d + maxPooling stack. 58 | // Note that we have more filters in the convolution. 59 | model.add(tf.layers.conv2d({ 60 | kernelSize: 5, 61 | filters: 16, 62 | strides: 1, 63 | activation: 'relu', 64 | kernelInitializer: 'varianceScaling' 65 | })); 66 | model.add(tf.layers.maxPooling2d({poolSize: [2, 2], strides: [2, 2]})); 67 | 68 | // Now we flatten the output from the 2D filters into a 1D vector to prepare 69 | // it for input into our last layer. This is common practice when feeding 70 | // higher dimensional data to a final classification output layer. 71 | model.add(tf.layers.flatten()); 72 | 73 | // Our last layer is a dense layer which has 10 output units, one for each 74 | // output class (i.e. 0, 1, 2, 3, 4, 5, 6, 7, 8, 9). 75 | const NUM_OUTPUT_CLASSES = 10; 76 | model.add(tf.layers.dense({ 77 | units: NUM_OUTPUT_CLASSES, 78 | kernelInitializer: 'varianceScaling', 79 | activation: 'softmax' 80 | })); 81 | 82 | 83 | // Choose an optimizer, loss function and accuracy metric, 84 | // then compile and return the model 85 | const optimizer = tf.train.adam(); 86 | model.compile({ 87 | optimizer: optimizer, 88 | loss: 'categoricalCrossentropy', 89 | metrics: ['accuracy'], 90 | }); 91 | 92 | return model; 93 | } 94 | 95 | async function train(model, data) { 96 | const metrics = ['loss', 'val_loss', 'acc', 'val_acc']; 97 | const container = { 98 | name: 'Model Training', styles: { height: '1000px' } 99 | }; 100 | const fitCallbacks = tfvis.show.fitCallbacks(container, metrics); 101 | 102 | const BATCH_SIZE = 512; 103 | const TRAIN_DATA_SIZE = 5500; 104 | const TEST_DATA_SIZE = 1000; 105 | 106 | const [trainXs, trainYs] = tf.tidy(() => { 107 | const d = data.nextTrainBatch(TRAIN_DATA_SIZE); 108 | return [ 109 | d.xs.reshape([TRAIN_DATA_SIZE, 28, 28, 1]), 110 | d.labels 111 | ]; 112 | }); 113 | 114 | const [testXs, testYs] = tf.tidy(() => { 115 | const d = data.nextTestBatch(TEST_DATA_SIZE); 116 | return [ 117 | d.xs.reshape([TEST_DATA_SIZE, 28, 28, 1]), 118 | d.labels 119 | ]; 120 | }); 121 | 122 | return model.fit(trainXs, trainYs, { 123 | batchSize: BATCH_SIZE, 124 | validationData: [testXs, testYs], 125 | epochs: 10, 126 | shuffle: true, 127 | callbacks: fitCallbacks 128 | }); 129 | } 130 | 131 | const classNames = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine']; 132 | 133 | function doPrediction(model, data, testDataSize = 500) { 134 | const IMAGE_WIDTH = 28; 135 | const IMAGE_HEIGHT = 28; 136 | const testData = data.nextTestBatch(testDataSize); 137 | const testxs = testData.xs.reshape([testDataSize, IMAGE_WIDTH, IMAGE_HEIGHT, 1]); 138 | const labels = testData.labels.argMax([-1]); 139 | const preds = model.predict(testxs).argMax([-1]); 140 | 141 | testxs.dispose(); 142 | return [preds, labels]; 143 | } 144 | 145 | async function showAccuracy(model, data) { 146 | const [preds, labels] = doPrediction(model, data); 147 | const classAccuracy = await tfvis.metrics.perClassAccuracy(labels, preds); 148 | const container = {name: 'Accuracy', tab: 'Evaluation'}; 149 | tfvis.show.perClassAccuracy(container, classAccuracy, classNames); 150 | 151 | labels.dispose(); 152 | } 153 | 154 | async function showConfusion(model, data) { 155 | const [preds, labels] = doPrediction(model, data); 156 | const confusionMatrix = await tfvis.metrics.confusionMatrix(labels, preds); 157 | const container = {name: 'Confusion Matrix', tab: 'Evaluation'}; 158 | tfvis.render.confusionMatrix( 159 | container, {values: confusionMatrix}, classNames); 160 | 161 | labels.dispose(); 162 | } 163 | 164 | async function run() { 165 | const data = new MnistData(); 166 | await data.load(); 167 | await showExamples(data); 168 | 169 | const model = getModel(); 170 | // tfvis.show.modelSummary({name: 'Model Architecture'}, model); 171 | await train(model, data); 172 | 173 | await showAccuracy(model, data); 174 | await showConfusion(model, data); 175 | } 176 | 177 | export { 178 | run 179 | } 180 | 181 | -------------------------------------------------------------------------------- /components/Client.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 133 | 134 | 136 | -------------------------------------------------------------------------------- /components/Logo.vue: -------------------------------------------------------------------------------- 1 | 20 | 35 | -------------------------------------------------------------------------------- /components/README.md: -------------------------------------------------------------------------------- 1 | # COMPONENTS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | The components directory contains your Vue.js Components. 6 | 7 | _Nuxt.js doesn't supercharge these components._ 8 | -------------------------------------------------------------------------------- /components/lang-switcher.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 28 | -------------------------------------------------------------------------------- /config/get-cookie.js: -------------------------------------------------------------------------------- 1 | const cookieparser = require('cookieparser') 2 | const getCookie = function (req) { 3 | if (req && req.headers && req.headers.cookie) { 4 | const parsed = cookieparser.parse(req.headers.cookie) 5 | return parsed 6 | } else { 7 | return {auth: null, lang: null} 8 | } 9 | } 10 | export default getCookie 11 | -------------------------------------------------------------------------------- /config/i18n.js: -------------------------------------------------------------------------------- 1 | import zh from './lang/zh' 2 | import en from './lang/en' 3 | 4 | // 加载element-ui语言包 5 | import enLocale from 'element-ui/lib/locale/lang/en' 6 | import zhLocale from 'element-ui/lib/locale/lang/zh-CN' 7 | 8 | const mergeZH = Object.assign(zhLocale, zh); 9 | const mergeEN = Object.assign(enLocale, en); 10 | const I18N = { 11 | locales: [{ 12 | code: 'en', 13 | iso: 'en-US', 14 | name: 'English' 15 | }, 16 | { 17 | code: 'zh', 18 | iso: 'zh-ZH', 19 | name: '中文' 20 | } 21 | ], 22 | defaultLocale: 'zh', 23 | vueI18n: { 24 | fallbackLocale: 'zh', 25 | messages: { 26 | en: mergeEN, 27 | zh: mergeZH 28 | } 29 | } 30 | } 31 | 32 | export default I18N 33 | -------------------------------------------------------------------------------- /config/lang/en.js: -------------------------------------------------------------------------------- 1 | const en = { 2 | enter: 'enter', 3 | config: 'Configure Clients', 4 | client_num:'client number', 5 | dataset:'dataset', 6 | post_task:'Post a Task', 7 | model:'model', 8 | reward:'reward', 9 | train_time:'train time (s)', 10 | train_process:'Train Progress', 11 | blockchain_info:'Blockchain Info', 12 | block_height:'block height', 13 | active_clients_number:'active clients number', 14 | total_power:'total power', 15 | update_time:'update time', 16 | current_block_reward:'current block reward', 17 | next_block_reward:'next block reward', 18 | process:'Process', 19 | pro1:'Configure Clients', 20 | pro2:'Post a Task', 21 | pro3:'Training', 22 | pro4:'Mining', 23 | pro5:'Finished', 24 | client:'client', 25 | state:'state', 26 | power:'power', 27 | samples:'samples', 28 | accuracy:'accuracy', 29 | loss:'loss', 30 | block_height1:'block height', 31 | reward1:'reward', 32 | select_a_dataset:'select a dataset', 33 | select_a_model:'select a model' 34 | } 35 | export default en 36 | -------------------------------------------------------------------------------- /config/lang/zh.js: -------------------------------------------------------------------------------- 1 | const zh = { 2 | enter: '进入', 3 | config: '配置客户端', 4 | client_num:'客户端数量', 5 | dataset:'数据集', 6 | post_task:'发布任务', 7 | model:'模型', 8 | reward:'奖励', 9 | train_time:'训练时间(s)', 10 | train_process: '训练进度', 11 | blockchain_info:'区块链信息', 12 | block_height:'区块高度', 13 | active_clients_number:'活跃节点数量', 14 | total_power:'总算力', 15 | update_time:'更新时间', 16 | current_block_reward:'当前区块奖励', 17 | next_block_reward:'下一区块奖励', 18 | process:'进度', 19 | pro1:'配置客户端', 20 | pro2:'发布任务', 21 | pro3:'训练', 22 | pro4:'挖矿', 23 | pro5:'完成', 24 | client:'客户端', 25 | state:'状态', 26 | power:'算力', 27 | samples:'数据集', 28 | accuracy:'准确率', 29 | loss:'损失', 30 | block_height1:'区块高度', 31 | reward1:'奖励', 32 | select_a_dataset:'选择一个数据集', 33 | select_a_model:'选择一个模型' 34 | 35 | 36 | 37 | } 38 | export default zh 39 | -------------------------------------------------------------------------------- /layouts/README.md: -------------------------------------------------------------------------------- 1 | # LAYOUTS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your Application Layouts. 6 | 7 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/views#layouts). 8 | -------------------------------------------------------------------------------- /layouts/default.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 39 | 45 | -------------------------------------------------------------------------------- /middleware/README.md: -------------------------------------------------------------------------------- 1 | # MIDDLEWARE 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your application middleware. 6 | Middleware let you define custom functions that can be run before rendering either a page or a group of pages. 7 | 8 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing#middleware). 9 | -------------------------------------------------------------------------------- /middleware/lang.js: -------------------------------------------------------------------------------- 1 | import getCookie from '@/config/get-cookie' 2 | 3 | export default function ({store, route, redirect, req}) { 4 | const {lang} = getCookie(req) 5 | if (lang) { 6 | store.commit('setLang', lang) 7 | } 8 | const routePath = route.path 9 | if (store.state.lang === 'en' && routePath.indexOf(`/${store.state.lang}/`) === -1) { 10 | console.log('redirect') 11 | return redirect({path: `/${store.state.lang}${routePath}`, query: route.query}) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /nuxt.config.js: -------------------------------------------------------------------------------- 1 | import I18N from './config/i18n' 2 | 3 | export default { 4 | mode: 'spa', 5 | /* 6 | ** Headers of the page 7 | */ 8 | head: { 9 | title: 'Federated Learning Blockchain', 10 | meta: [ 11 | { charset: 'utf-8' }, 12 | { name: 'viewport', content: 'width=device-width, initial-scale=1' }, 13 | { hid: 'description', name: 'description', content: process.env.npm_package_description || '' } 14 | ], 15 | link: [ 16 | { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' } 17 | ] 18 | }, 19 | /* 20 | ** Customize the progress-bar color 21 | */ 22 | loading: { color: '#fff' }, 23 | /* 24 | ** Global CSS 25 | */ 26 | css: [ 27 | 'element-ui/lib/theme-chalk/index.css', 28 | '@/assets/css/main.css' 29 | ], 30 | /* 31 | ** Plugins to load before mounting the App 32 | */ 33 | plugins: [ 34 | '@/plugins/element-ui' 35 | ], 36 | /* 37 | ** Nuxt.js dev-modules 38 | */ 39 | buildModules: [ 40 | ], 41 | /* 42 | ** Nuxt.js modules 43 | */ 44 | modules: [ 45 | ['nuxt-i18n', I18N], 46 | ], 47 | /* 48 | ** Build configuration 49 | */ 50 | build: { 51 | transpile: [/^element-ui/], 52 | /* 53 | ** You can extend webpack config here 54 | */ 55 | extend (config, ctx) { 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flb-web", 3 | "version": "1.0.0", 4 | "description": "flb-demo-project", 5 | "author": "sunshuai", 6 | "private": true, 7 | "scripts": { 8 | "dev": "nuxt", 9 | "build": "nuxt build", 10 | "start": "nuxt start", 11 | "generate": "nuxt generate" 12 | }, 13 | "dependencies": { 14 | "@tensorflow/tfjs": "^2.0.0", 15 | "@tensorflow/tfjs-vis": "^1.4.0", 16 | "cookieparser": "^0.1.0", 17 | "element-ui": "^2.4.11", 18 | "nuxt": "^2.0.0", 19 | "nuxt-i18n": "^6.12.2" 20 | }, 21 | "devDependencies": {} 22 | } 23 | -------------------------------------------------------------------------------- /pages/README.md: -------------------------------------------------------------------------------- 1 | # PAGES 2 | 3 | This directory contains your Application Views and Routes. 4 | The framework reads all the `*.vue` files inside this directory and creates the router of your application. 5 | 6 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing). 7 | -------------------------------------------------------------------------------- /pages/home.vue: -------------------------------------------------------------------------------- 1 | 171 | 172 | 388 | 389 | 398 | -------------------------------------------------------------------------------- /pages/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 27 | 28 | 86 | -------------------------------------------------------------------------------- /pages/test.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 17 | 18 | 21 | -------------------------------------------------------------------------------- /pages/testcar.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 204 | 205 | 208 | -------------------------------------------------------------------------------- /pages/testmnist.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 19 | 20 | 23 | -------------------------------------------------------------------------------- /plugins/README.md: -------------------------------------------------------------------------------- 1 | # PLUGINS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains Javascript plugins that you want to run before mounting the root Vue.js application. 6 | 7 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/plugins). 8 | -------------------------------------------------------------------------------- /plugins/element-ui.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Element from 'element-ui' 3 | 4 | // Vue.use(Element, {locale}) 5 | 6 | 7 | export default ({app}) => { 8 | Vue.use(Element, { 9 | i18n: (key, value) => app.i18n.t(key, value) 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /static/README.md: -------------------------------------------------------------------------------- 1 | # STATIC 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your static files. 6 | Each file inside this directory is mapped to `/`. 7 | Thus you'd want to delete this README.md before deploying to production. 8 | 9 | Example: `/static/robots.txt` is mapped as `/robots.txt`. 10 | 11 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#static). 12 | -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blockchain-neu/federated-learning-blockchain-web/7b85fb9a4b75068afdf1683b680719cefd89af85/static/favicon.ico -------------------------------------------------------------------------------- /static/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blockchain-neu/federated-learning-blockchain-web/7b85fb9a4b75068afdf1683b680719cefd89af85/static/github.png -------------------------------------------------------------------------------- /static/mnist/mnist_images.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blockchain-neu/federated-learning-blockchain-web/7b85fb9a4b75068afdf1683b680719cefd89af85/static/mnist/mnist_images.png -------------------------------------------------------------------------------- /store/README.md: -------------------------------------------------------------------------------- 1 | # STORE 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your Vuex Store files. 6 | Vuex Store option is implemented in the Nuxt.js framework. 7 | 8 | Creating a file in this directory automatically activates the option in the framework. 9 | 10 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/vuex-store). 11 | -------------------------------------------------------------------------------- /store/index.js: -------------------------------------------------------------------------------- 1 | export const state = () => ({ 2 | powerList: [] 3 | }) 4 | import Vue from 'vue'; 5 | 6 | function compare(property) { 7 | return function (a, b) { 8 | return b[property] - a[property]; 9 | } 10 | 11 | } 12 | 13 | export const getters = { 14 | candidates: state => { 15 | let a = state.powerList.slice(0) 16 | a.sort(compare('power')) 17 | let winnerId = 0 18 | //3个及以下时,选算力最大的那个;3个以上时,从算力最大的前3个中随机选择 19 | if (a.length < 4) { 20 | winnerId = a[0].id 21 | } else { 22 | let items = a.slice(0, 4) 23 | winnerId = items[Math.floor(Math.random() * items.length)].id 24 | } 25 | return winnerId 26 | } 27 | } 28 | 29 | export const mutations = { 30 | 31 | add(state, data) { 32 | state.powerList.push({ 33 | id: data.id, 34 | power: data.power 35 | }) 36 | }, 37 | remove(state, data) { 38 | state.powerList.splice(state.powerList.findIndex((item) => item.id === data.id), 1) 39 | }, 40 | update(state, data) { 41 | Vue.set(state.powerList.find((item) => item.id === data.id), 'power', data.power) 42 | } 43 | 44 | 45 | } 46 | -------------------------------------------------------------------------------- /store/lang.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | 3 | export const mutations = { 4 | //国际化 5 | setLang(state, lang) { 6 | state.lang = lang 7 | } 8 | } 9 | --------------------------------------------------------------------------------