├── .eslintrc.js ├── .gitignore ├── CHANGELOG.md ├── CODE_TAG ├── LICENSE ├── README.md ├── SConstruct ├── index.html ├── package.json ├── public ├── css │ └── font-awesome.min.css ├── favicon.ico └── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ ├── fontawesome-webfont.woff2 │ └── password.woff2 ├── src ├── AccountAppearance.vue ├── AccountSettings.vue ├── AccountTeams.vue ├── AccountView.vue ├── App.vue ├── Award.vue ├── Button.vue ├── ChartsDialog.vue ├── ChartsView.vue ├── ClientVersion.vue ├── CommonSettings.vue ├── ConnectDialog.vue ├── Dialog.vue ├── DragList.vue ├── FAHLogo.vue ├── GPUFieldset.vue ├── GroupSettings.vue ├── HelpBalloon.vue ├── ImageInput.vue ├── InfoItem.vue ├── LogView.vue ├── LoginDialog.vue ├── MachineDetailsView.vue ├── MachineGroup.vue ├── MachineMux.vue ├── MachineView.vue ├── MachinesView.vue ├── MainHeader.vue ├── MainMenu.vue ├── MessageDialog.vue ├── NewAccountDialog.vue ├── NewsView.vue ├── Pacify.vue ├── PauseDialog.vue ├── PlotView.vue ├── ProgressBar.vue ├── ProjectView.vue ├── ProjectsView.vue ├── ResetView.vue ├── SettingsView.vue ├── StatsView.vue ├── TeamDialog.vue ├── UnitDetailsView.vue ├── UnitField.vue ├── UnitHeader.vue ├── UnitHeaders.vue ├── UnitInfo.vue ├── UnitsView.vue ├── VerifyView.vue ├── ViewHeader.vue ├── Visualization.vue ├── WUsView.vue ├── account.js ├── api-sock.js ├── api.js ├── base.styl ├── bip39.js ├── cache.js ├── chart.js ├── crypto.js ├── dark.styl ├── data-series.js ├── direct-mach-conn.js ├── light.styl ├── mach-connection.js ├── machine.js ├── machines.js ├── main.js ├── matrix.js ├── news.js ├── node-mach-conn.js ├── node.js ├── projects.js ├── router.js ├── sock.js ├── stats.js ├── subscriber.js ├── unit.js ├── unit_fields.json ├── updatable.js ├── util.js └── viewer │ ├── InfiniteGridHelper.js │ ├── Sky.js │ ├── grid.frag │ ├── grid.vert │ ├── sky.frag │ └── sky.vert └── vite.config.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | node: true, 4 | }, 5 | extends: [ 6 | 'eslint:recommended', 7 | 'plugin:vue/vue3-recommended', 8 | ], 9 | rules: { 10 | "vue/max-attributes-per-line": 'off', 11 | "max-len": ['error', { code: 120, ignoreUrls: true, ignoreComments: true }], 12 | "vue/html-closing-bracket-newline": ["error", { 13 | "singleline": "never", 14 | "multiline": "never" 15 | }], 16 | "vue/singleline-html-element-content-newline": 'off' 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | /dist 4 | /dist.txt 5 | /*.tar.bz2 6 | /*.zip 7 | *~ 8 | /package-lock.json 9 | /.sconsign.dblite 10 | /.sconf_temp 11 | /config.log 12 | /.env.local 13 | /.env.*.local 14 | /crap 15 | -------------------------------------------------------------------------------- /CODE_TAG: -------------------------------------------------------------------------------- 1 | This file is part of the Folding@home Client. 2 | 3 | The fah-client runs Folding@home protein folding simulations. 4 | Copyright (c) 2001-2024, foldingathome.org 5 | All rights reserved. 6 | 7 | This program is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License along 18 | with this program; if not, write to the Free Software Foundation, Inc., 19 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | 21 | For information regarding this software email: 22 | Joseph Coffland 23 | joseph@cauldrondevelopment.com 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Folding@home Bastet Web Control 2 | =============================== 3 | 4 | This is the frontend Web app for the Folding@home v8 client, codenamed Bastet. 5 | See Also: https://github.com/FoldingAtHome/fah-client-bastet 6 | 7 | # Debian Linux Quick Start 8 | 9 | ## Get the code 10 | 11 | git clone https://github.com/foldingathome/fah-web-client-bastet 12 | 13 | ## Start the development web server 14 | 15 | cd fah-web-client-bastet 16 | npm i 17 | npm run dev 18 | 19 | ## Open the Browser 20 | 21 | With the development server running visit http://localhost:5173/ 22 | -------------------------------------------------------------------------------- /SConstruct: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | env = Environment(ENV = os.environ) 4 | try: 5 | paths = [os.environ.get('CBANG_HOME'), os.environ.get('CBANG_CONFIG_HOME')] 6 | env.Tool('config', toolpath = paths) 7 | except Exception as e: 8 | raise Exception('CBANG_HOME not set?\n' + str(e)) 9 | 10 | env.CBLoadTools('dist packager') 11 | conf = env.CBConfigure() 12 | 13 | with open('package.json', 'r') as f: package_info = json.load(f) 14 | 15 | env.Replace(PACKAGE_VERSION = package_info['version']) 16 | env.Replace(dist_build = '') 17 | conf.Finish() 18 | 19 | if 'dist' in COMMAND_LINE_TARGETS: 20 | if not env.GetOption('clean'): 21 | env.RunCommandOrRaise(['npm', 'install']) 22 | env.RunCommandOrRaise(['npm', 'run', 'build']) 23 | 24 | distfiles = ['dist', 'LICENSE'] 25 | tar = env.ZipDist(package_info['name'], distfiles) 26 | AlwaysBuild(tar) 27 | Alias('dist', tar) 28 | Clean(tar, ['dist', 'dist.txt']) 29 | 30 | if 'distclean' in COMMAND_LINE_TARGETS: 31 | Clean('distclean', ['.sconsign.dblite', '.sconf_temp', 'config.log', 32 | 'node_modules', 'package-lock.json', 33 | Glob('*.tar.bz2'), Glob('*.zip'),'dist', 'dist.txt']) 34 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Folding@home Client 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fah-web-control", 3 | "version": "8.5.3", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build", 7 | "serve": "vite preview" 8 | }, 9 | "dependencies": { 10 | "npm-check-updates": "^16.10.8", 11 | "pako": "^2.1.0", 12 | "three": "^0.151.3", 13 | "vue": "^3.2.47", 14 | "vue-router": "^4.1.6" 15 | }, 16 | "devDependencies": { 17 | "@vitejs/plugin-vue": "^6.0.1", 18 | "@vue/compiler-sfc": "^3.2.47", 19 | "pug": "^3.0.2", 20 | "stylus": "^0.59.0", 21 | "vite": "^7.1.4" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FoldingAtHome/fah-web-client-bastet/67a738ea4785363d1a8cd913e4fd7323594e1e3c/public/favicon.ico -------------------------------------------------------------------------------- /public/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FoldingAtHome/fah-web-client-bastet/67a738ea4785363d1a8cd913e4fd7323594e1e3c/public/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FoldingAtHome/fah-web-client-bastet/67a738ea4785363d1a8cd913e4fd7323594e1e3c/public/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FoldingAtHome/fah-web-client-bastet/67a738ea4785363d1a8cd913e4fd7323594e1e3c/public/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FoldingAtHome/fah-web-client-bastet/67a738ea4785363d1a8cd913e4fd7323594e1e3c/public/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FoldingAtHome/fah-web-client-bastet/67a738ea4785363d1a8cd913e4fd7323594e1e3c/public/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /public/fonts/password.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FoldingAtHome/fah-web-client-bastet/67a738ea4785363d1a8cd913e4fd7323594e1e3c/public/fonts/password.woff2 -------------------------------------------------------------------------------- /src/AccountAppearance.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 64 | 65 | 99 | 100 | 137 | -------------------------------------------------------------------------------- /src/AccountSettings.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 83 | 84 | 151 | 152 | 159 | -------------------------------------------------------------------------------- /src/AccountTeams.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 107 | 108 | 150 | 151 | 174 | -------------------------------------------------------------------------------- /src/Award.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 71 | 72 | 82 | 83 | 100 | -------------------------------------------------------------------------------- /src/Button.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 115 | 116 | 123 | 124 | 196 | -------------------------------------------------------------------------------- /src/ChartsDialog.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 41 | 42 | 65 | 66 | 95 | -------------------------------------------------------------------------------- /src/ClientVersion.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 42 | 43 | 54 | 55 | 66 | -------------------------------------------------------------------------------- /src/CommonSettings.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 56 | 57 | 103 | 104 | 106 | -------------------------------------------------------------------------------- /src/ConnectDialog.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 54 | 55 | 62 | 63 | 69 | -------------------------------------------------------------------------------- /src/Dialog.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 115 | 116 | 132 | 133 | 183 | -------------------------------------------------------------------------------- /src/FAHLogo.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 35 | 36 | 61 | -------------------------------------------------------------------------------- /src/GPUFieldset.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 54 | 55 | 78 | 79 | 81 | -------------------------------------------------------------------------------- /src/HelpBalloon.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 42 | 43 | 55 | 56 | 132 | -------------------------------------------------------------------------------- /src/ImageInput.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 149 | 150 | 159 | 160 | 193 | -------------------------------------------------------------------------------- /src/InfoItem.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 38 | 39 | 44 | 45 | 79 | -------------------------------------------------------------------------------- /src/MachineDetailsView.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 43 | 44 | 80 | 81 | 87 | -------------------------------------------------------------------------------- /src/MachineGroup.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 72 | 73 | 110 | 111 | 146 | -------------------------------------------------------------------------------- /src/MachineMux.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 43 | 44 | 47 | 48 | 50 | -------------------------------------------------------------------------------- /src/MachinesView.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 54 | 55 | 96 | 97 | 129 | -------------------------------------------------------------------------------- /src/MainHeader.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 38 | 39 | 57 | 58 | 63 | -------------------------------------------------------------------------------- /src/MainMenu.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 34 | 35 | 43 | 44 | 72 | -------------------------------------------------------------------------------- /src/MessageDialog.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 88 | 89 | 97 | 98 | 104 | -------------------------------------------------------------------------------- /src/NewAccountDialog.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 72 | 73 | 99 | 100 | 120 | -------------------------------------------------------------------------------- /src/NewsView.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 39 | 40 | 61 | 62 | 97 | -------------------------------------------------------------------------------- /src/Pacify.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 51 | 52 | 57 | 58 | 73 | -------------------------------------------------------------------------------- /src/PauseDialog.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 46 | 47 | 54 | 55 | 57 | -------------------------------------------------------------------------------- /src/PlotView.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 133 | 134 | 137 | 138 | 142 | -------------------------------------------------------------------------------- /src/ProgressBar.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 34 | 35 | 40 | 41 | 65 | -------------------------------------------------------------------------------- /src/ProjectView.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 58 | 59 | 81 | 82 | 117 | -------------------------------------------------------------------------------- /src/ProjectsView.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 34 | 35 | 48 | 49 | 58 | -------------------------------------------------------------------------------- /src/ResetView.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 67 | 68 | 118 | 119 | 143 | -------------------------------------------------------------------------------- /src/TeamDialog.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 79 | 80 | 116 | 117 | 122 | -------------------------------------------------------------------------------- /src/UnitDetailsView.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 71 | 72 | 145 | 146 | 154 | -------------------------------------------------------------------------------- /src/UnitField.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 35 | 36 | 43 | 44 | 75 | -------------------------------------------------------------------------------- /src/UnitHeader.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 39 | 40 | 44 | 45 | 47 | -------------------------------------------------------------------------------- /src/UnitHeaders.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 39 | 40 | 47 | 48 | 50 | -------------------------------------------------------------------------------- /src/UnitInfo.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 35 | 36 | 41 | 42 | 44 | -------------------------------------------------------------------------------- /src/UnitsView.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 42 | 43 | 52 | 53 | 85 | -------------------------------------------------------------------------------- /src/VerifyView.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 47 | 48 | 70 | 71 | 75 | -------------------------------------------------------------------------------- /src/ViewHeader.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 76 | 77 | 106 | 107 | 188 | -------------------------------------------------------------------------------- /src/api-sock.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************\ 2 | 3 | This file is part of the Folding@home Client. 4 | 5 | The fah-client runs Folding@home protein folding simulations. 6 | Copyright (c) 2001-2024, foldingathome.org 7 | All rights reserved. 8 | 9 | This program is free software; you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation; either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License along 20 | with this program; if not, write to the Free Software Foundation, Inc., 21 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 22 | 23 | For information regarding this software 358,797,681email: 24 | Joseph Coffland 25 | joseph@cauldrondevelopment.com 26 | 27 | \******************************************************************************/ 28 | 29 | import Sock from './sock.js' 30 | import Subscriber from './subscriber.js' 31 | 32 | 33 | class TeamSubscriber extends Subscriber { 34 | constructor(sock, team, max_count = 10000) { 35 | super(sock, max_count) 36 | this.ref = `team-${team}` 37 | this.msg = {timeseries: 'team.score', team, '$ref': this.ref} 38 | } 39 | } 40 | 41 | 42 | class UserSubscriber extends Subscriber { 43 | constructor(sock, uid, pid, max_count = 10000) { 44 | super(sock, max_count) 45 | this.ref = `user-${uid}-${pid}` 46 | this.msg = {timeseries: 'user.score', uid, pid, '$ref': this.ref} 47 | } 48 | } 49 | 50 | 51 | function get_chart_subscriber(sock, chart) { 52 | switch (chart.type) { 53 | case 'team': return new TeamSubscriber(sock, chart.team) 54 | case 'user': return new UserSubscriber(sock, chart.uid, chart.pid) 55 | } 56 | } 57 | 58 | 59 | class APISock extends Sock { 60 | constructor(ctx, ...args) { 61 | super(...args) 62 | this.ctx = ctx 63 | this.subs = {} 64 | this.nextID = 1 65 | } 66 | 67 | 68 | subscribe(chart, cb) { 69 | let sub = get_chart_subscriber(this, chart) 70 | let ref = sub.ref 71 | 72 | if (this.subs[ref] == undefined) this.subs[ref] = sub 73 | 74 | return {ref, id: this.subs[ref].add_subscriber(cb)} 75 | } 76 | 77 | 78 | unsubscribe(o) {this.subs[o.ref].del_subscriber(o.id)} 79 | 80 | 81 | on_message(msg) { 82 | if (msg.data != undefined && msg.data.message != undefined) { 83 | console.error(msg.data.message) 84 | console.debug(msg) 85 | return 86 | } 87 | 88 | let sub = this.subs[msg.$ref] 89 | if (sub != undefined) sub.on_message(msg) 90 | 91 | else throw 'Unsupported API Websocket message: ' + JSON.stringify(msg) 92 | } 93 | 94 | 95 | on_open(event) {Object.values(this.subs).map(t => t.on_open(event))} 96 | 97 | 98 | on_close(event) { 99 | setTimeout(() => this.connect(), 1000) 100 | Object.values(this.subs).map(t => t.on_close(event)) 101 | } 102 | 103 | 104 | on_error(event) {console.debug('APISock error', event)} 105 | } 106 | 107 | 108 | export default APISock 109 | -------------------------------------------------------------------------------- /src/cache.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************\ 2 | 3 | This file is part of the Folding@home Client. 4 | 5 | The fah-client runs Folding@home protein folding simulations. 6 | Copyright (c) 2001-2024, foldingathome.org 7 | All rights reserved. 8 | 9 | This program is free software; you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation; either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License along 20 | with this program; if not, write to the Free Software Foundation, Inc., 21 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 22 | 23 | For information regarding this software email: 24 | Joseph Coffland 25 | joseph@cauldrondevelopment.com 26 | 27 | \******************************************************************************/ 28 | 29 | class Cache { 30 | constructor(name, timeout) { 31 | this.name = name 32 | this.timeout = timeout 33 | } 34 | 35 | 36 | async set(key, value, status) { 37 | let data = {ts: new Date().toISOString(), value, status} 38 | 39 | try { 40 | if (!this.cache) this.cache = await caches.open(this.name) 41 | await this.cache.put(key, new Response(JSON.stringify(data))) 42 | 43 | } catch (e) { 44 | if (!this._cache) this._cache = {} 45 | this._cache[key] = data 46 | } 47 | } 48 | 49 | 50 | async get(key, timeout, withStatus = false) { 51 | let data 52 | if (timeout == undefined) timeout = this.timeout 53 | 54 | try { 55 | if (!this.cache) this.cache = await caches.open(this.name) 56 | 57 | let res = await this.cache.match(key) 58 | if (!res) return 59 | data = await res.json() 60 | 61 | } catch (e) { 62 | if (!this._cache) this._cache = {} 63 | data = this._cache[key] 64 | } 65 | 66 | if (data && 67 | (!timeout || Date.now() - new Date(data.ts).getTime() < timeout)) 68 | return withStatus ? data : data.value 69 | } 70 | } 71 | 72 | 73 | export default Cache 74 | -------------------------------------------------------------------------------- /src/dark.styl: -------------------------------------------------------------------------------- 1 | /******************************************************************************\ 2 | 3 | This file is part of the Folding@home Client. 4 | 5 | The fah-client runs Folding@home protein folding simulations. 6 | Copyright (c) 2001-2024, foldingathome.org 7 | All rights reserved. 8 | 9 | This program is free software; you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation; either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License along 20 | with this program; if not, write to the Free Software Foundation, Inc., 21 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 22 | 23 | For information regarding this software email: 24 | Joseph Coffland 25 | joseph@cauldrondevelopment.com 26 | 27 | \******************************************************************************/ 28 | 29 | body.theme-dark 30 | --highlight-color #ffbe42 31 | --secondary-color #82cdb1 32 | 33 | --body-bg #0b121d 34 | --body-fg #fff 35 | 36 | --header-fg #fff 37 | --header-bg #0d0d0f 38 | 39 | --input-bg #0b121d 40 | --input-border-color #666 41 | 42 | --panel-bg #0d0d0f 43 | 44 | --code-bg #888 45 | --code-fg #eee 46 | 47 | --border-color #223c5f 48 | 49 | --button-icon-fg #eee 50 | --button-disabled-fg #666 51 | 52 | --title-color #8cb5f1 53 | 54 | --log-fg #7f7f7f 55 | --log-bg #000 56 | 57 | --overlay-bg rgba(64, 64, 64, 0.6) 58 | 59 | --shadow-color #000 60 | 61 | --table-border-color #000 62 | --table-header-bg #2a3d4f 63 | --table-header-fg #f1f1f1 64 | --table-even #0f1319 65 | --table-odd #1a2835 66 | 67 | --border-radius 3px 68 | --border 1px solid var(--border-color) 69 | 70 | --disconnected-bg #333 71 | 72 | .machine-view .machine-group-header 73 | border-top var(--border) 74 | -------------------------------------------------------------------------------- /src/data-series.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************\ 2 | 3 | This file is part of the Folding@home Client. 4 | 5 | The fah-client runs Folding@home protein folding simulations. 6 | Copyright (c) 2001-2024, foldingathome.org 7 | All rights reserved. 8 | 9 | This program is free software; you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation; either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License along 20 | with this program; if not, write to the Free Software Foundation, Inc., 21 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 22 | 23 | For information regarding this software email: 24 | Joseph Coffland 25 | joseph@cauldrondevelopment.com 26 | 27 | \******************************************************************************/ 28 | 29 | import {reactive} from 'vue' 30 | 31 | 32 | class DataSeries { 33 | constructor(color, enabled = true) { 34 | this.color = color 35 | this.enabled = enabled 36 | this.state = reactive({ 37 | data: [], 38 | min: {x: Infinity, y: Infinity}, 39 | max: {x: -Infinity, y: -Infinity}, 40 | }) 41 | } 42 | 43 | 44 | get data() {return this.state.data} 45 | get max() {return this.state.max} 46 | get min() {return this.state.min} 47 | 48 | 49 | add(data) { 50 | this.state.data.push(data) 51 | 52 | this.state.min.x = Math.min(this.state.min.x, data.x) 53 | this.state.max.x = Math.max(this.state.max.x, data.x) 54 | this.state.min.y = Math.min(this.state.min.y, data.y) 55 | this.state.max.y = Math.max(this.state.max.y, data.y) 56 | } 57 | 58 | 59 | find_nearest_x(x) { 60 | let data = this.state.data 61 | if (!data.length) return 62 | 63 | let i = this._find_index(data, x, 0, data.length) 64 | 65 | if (i == data.length) return data[i - 1] 66 | if (i == 0) return data[0] 67 | return data[x - data[i - 1].x < data[i].x - x ? i - 1 : i] 68 | } 69 | 70 | 71 | _find_index(data, x, min, max) { 72 | let len = max - min 73 | 74 | if (len == 1) return data[min].x < x ? min + 1 : min 75 | 76 | let mid = Math.floor(len / 2) + min 77 | 78 | if (x < data[mid].x) return this._find_index(data, x, min, mid) 79 | return this._find_index(data, x, mid, max) 80 | } 81 | } 82 | 83 | 84 | export default DataSeries 85 | -------------------------------------------------------------------------------- /src/direct-mach-conn.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************\ 2 | 3 | This file is part of the Folding@home Client. 4 | 5 | The fah-client runs Folding@home protein folding simulations. 6 | Copyright (c) 2001-2024, foldingathome.org 7 | All rights reserved. 8 | 9 | This program is free software; you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation; either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License along 20 | with this program; if not, write to the Free Software Foundation, Inc., 21 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 22 | 23 | For information regarding this software email: 24 | Joseph Coffland 25 | joseph@cauldrondevelopment.com 26 | 27 | \******************************************************************************/ 28 | 29 | import MachConnection from './mach-connection.js' 30 | import Sock from './sock.js' 31 | 32 | 33 | class DirectMachConn extends MachConnection { 34 | constructor(ctx, name, address = '127.0.0.1:7396') { 35 | super(ctx.$machs.create(undefined)) 36 | 37 | this.ctx = ctx 38 | this.initialized = false 39 | 40 | this.mach.set_conn(this) 41 | this.set_address(address) 42 | this.ctx.$machs.set('__direct__', this.mach) 43 | } 44 | 45 | 46 | set_address(address) { 47 | if (this.address == address) return 48 | this.address = address 49 | 50 | if (this.sock) { 51 | this.sock.on_open = () => {} 52 | this.sock.on_close = () => {} 53 | this.sock.on_message = () => {} 54 | this.sock.close() 55 | delete this.sock 56 | this._on_close() 57 | } 58 | 59 | this.mach.set_name('direct') 60 | this.mach.state.data = {} 61 | 62 | let url = 'ws://' + address + '/api/websocket' 63 | this.sock = new Sock(url) 64 | this.sock.on_open = () => this._on_open() 65 | this.sock.on_close = event => this._on_close(event) 66 | this.sock.on_message = msg => this._on_message(msg) 67 | 68 | this.open() 69 | } 70 | 71 | 72 | open() {this.sock.connect()} 73 | 74 | 75 | // From MachConnection 76 | is_connected() {return this.sock.connected} 77 | is_direct() {return true} 78 | async send(msg) {return this.sock.send(msg)} 79 | 80 | 81 | _clear_ping() { 82 | if (this._ping_timer != undefined) clearTimeout(this._ping_timer) 83 | delete this._ping_timer 84 | } 85 | 86 | 87 | _update_ping() { 88 | if (this.ctx.$util.version_less('8.1.17', this.mach.get_version())) { 89 | this._clear_ping() 90 | this._ping_timer = setTimeout(() => { 91 | console.log(this.mach.get_name() + ': timed out') 92 | this.sock.close() 93 | }, 30000) 94 | } 95 | } 96 | 97 | 98 | _on_open(event) {this.on_open()} 99 | 100 | 101 | _on_close(event) { 102 | this._clear_ping() 103 | this.on_close() 104 | this.initialized = false 105 | if (this.sock) setTimeout(() => this.sock.connect(), 1000) 106 | } 107 | 108 | 109 | _on_message(msg) { 110 | this._update_ping() 111 | this.on_message(msg) 112 | 113 | if (!this.initialized) { 114 | let info = this.mach.get_info() 115 | 116 | if (info.version) { 117 | this.initialized = true 118 | 119 | // Check versions, reload Web Control if out of date 120 | console.debug('Direct Client Version', info.version) 121 | let last_version = this.ctx.$util.retrieve('fah-last-version') 122 | let our_version = import.meta.env.PACKAGE_VERSION 123 | 124 | if (this.ctx.$util.version_less(our_version, info.version) && 125 | (!last_version || 126 | this.ctx.$util.version_less(last_version, info.version))) { 127 | this.ctx.$util.store('fah-last-version', info.version) 128 | 129 | if (location.hostname.indexOf('foldingathome.org') != -1) { 130 | if (!info.url) location.reload(true) 131 | else location.replace(info.url) 132 | } 133 | } 134 | 135 | // Set direct connection 136 | if (info.id) { 137 | let node_mach = this.ctx.$machs.get(info.id) 138 | if (node_mach) this.mach.dup_state(node_mach) 139 | this.mach.state.id = info.id 140 | } 141 | 142 | // Update machine name 143 | if (info.mach_name) this.mach.set_name(info.mach_name) 144 | 145 | this.mach.auto_link() 146 | } 147 | } 148 | } 149 | } 150 | 151 | 152 | export default DirectMachConn 153 | -------------------------------------------------------------------------------- /src/light.styl: -------------------------------------------------------------------------------- 1 | /******************************************************************************\ 2 | 3 | This file is part of the Folding@home Client. 4 | 5 | The fah-client runs Folding@home protein folding simulations. 6 | Copyright (c) 2001-2024, foldingathome.org 7 | All rights reserved. 8 | 9 | This program is free software; you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation; either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License along 20 | with this program; if not, write to the Free Software Foundation, Inc., 21 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 22 | 23 | For information regarding this software email: 24 | Joseph Coffland 25 | joseph@cauldrondevelopment.com 26 | 27 | \******************************************************************************/ 28 | 29 | body 30 | --highlight-color #444 31 | --secondary-color #888 32 | 33 | --error-color #b51100 34 | --success-color #4caf50 35 | --warn-color #fb0 36 | 37 | --body-bg #e7e7e7 38 | --body-fg #333 39 | 40 | --header-fg #333 41 | --header-bg #fff 42 | 43 | --input-bg #fff 44 | --input-border-color #eee 45 | --input-border 2px inset var(--input-border-color) 46 | 47 | --panel-bg #fff 48 | --panel-fg var(--body-fg) 49 | 50 | --link-color #ee9322 51 | --link-alt #55aaff 52 | 53 | --logo-color var(--header-fg) 54 | 55 | --log-fg var(--panel-fg) 56 | --log-bg var(--panel-bg) 57 | 58 | --title-color inherit 59 | --subtitle-color var(--secondary-color) 60 | 61 | --button-bg #0b5ed7 62 | --button-fg #fff 63 | --button-icon-fg #000 64 | --button-success var(--success-color) 65 | --button-caution var(--error-color) 66 | --button-disabled-bg #aaa 67 | --button-disabled-fg #ddd 68 | 69 | --pacify-bg rgba(0, 0, 0, 0.6) 70 | 71 | --overlay-bg rgba(0, 0, 0, 0.4) 72 | --overlay-fg #fff 73 | 74 | --code-bg #ddd 75 | --code-fg #444 76 | 77 | --border-color var(--panel-bg) 78 | 79 | --table-border-color #ddd 80 | --table-border 1px solid var(--table-border-color) 81 | --table-header-bg #dedede 82 | --table-header-fg #333 83 | --table-even #fff 84 | --table-odd #f3f3f3 85 | 86 | --shadow-color #222 87 | --shadow 3px 3px 12px var(--shadow-color) 88 | 89 | --border-radius 0 90 | --border 0 91 | 92 | --disconnected-bg #ddd 93 | -------------------------------------------------------------------------------- /src/mach-connection.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************\ 2 | 3 | This file is part of the Folding@home Client. 4 | 5 | The fah-client runs Folding@home protein folding simulations. 6 | Copyright (c) 2001-2024, foldingathome.org 7 | All rights reserved. 8 | 9 | This program is free software; you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation; either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License along 20 | with this program; if not, write to the Free Software Foundation, Inc., 21 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 22 | 23 | For information regarding this software email: 24 | Joseph Coffland 25 | joseph@cauldrondevelopment.com 26 | 27 | \******************************************************************************/ 28 | 29 | class MachConnection { 30 | constructor(mach) {this.mach = mach} 31 | 32 | is_connected() {return false} 33 | is_direct() {return false} 34 | 35 | get_id() {return this.mach.get_id()} 36 | 37 | on_open() {this.mach.on_open()} 38 | on_close() {this.mach.on_close()} 39 | on_message(msg) {this.mach.on_message(msg)} 40 | 41 | async send(msg) {} 42 | async receive(msg) {} 43 | close() {} 44 | } 45 | 46 | export default MachConnection 47 | -------------------------------------------------------------------------------- /src/machines.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************\ 2 | 3 | This file is part of the Folding@home Client. 4 | 5 | The fah-client runs Folding@home protein folding simulations. 6 | Copyright (c) 2001-2024, foldingathome.org 7 | All rights reserved. 8 | 9 | This program is free software; you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation; either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License along 20 | with this program; if not, write to the Free Software Foundation, Inc., 21 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 22 | 23 | For information regarding this software email: 24 | Joseph Coffland 25 | joseph@cauldrondevelopment.com 26 | 27 | \******************************************************************************/ 28 | 29 | import {watchEffect, reactive, toRaw} from 'vue' 30 | import Machine from './machine.js' 31 | import Unit from './unit.js' 32 | 33 | 34 | class Machines { 35 | constructor(ctx) { 36 | this.ctx = ctx 37 | this.machines = reactive({}) 38 | 39 | watchEffect(() => { 40 | if (!ctx.$account.data.machines) return 41 | 42 | // Add new machines and set machine name 43 | let found = {} 44 | for (let config of ctx.$account.data.machines) { 45 | let mach = this.get(config.id) 46 | if (!mach) mach = this.add(config.id) 47 | mach.set_name(config.name) 48 | found[config.id] = true 49 | } 50 | 51 | // Erase removed machines 52 | for (let mach of this) 53 | if (!found[mach.get_id()]) { 54 | if (mach.is_direct()) continue // Don't remove direct connections 55 | mach.close() 56 | this.del(mach.get_id()) 57 | } 58 | }) 59 | } 60 | 61 | 62 | get is_empty() {return !this.count} 63 | get count() {return Array.from(this).length} 64 | 65 | 66 | *[Symbol.iterator]() { 67 | for (let mach of Object.values(this.machines)) 68 | if (!mach.is_hidden()) yield mach 69 | } 70 | 71 | 72 | set(id, mach) { 73 | if (this.machines[id]) { 74 | if (toRaw(this.machines[id]) == mach) return 75 | this.machines[id].close() 76 | } 77 | 78 | this.machines[id] = mach 79 | mach.wus_enable(this.wus_enabled) 80 | } 81 | 82 | 83 | has(id) {return id in this.machines} 84 | get(id) {return this.machines[id]} 85 | add(id) {this.set(id, this.create(id)); return this.get(id)} 86 | del(id) {delete this.machines[id]} 87 | create(id) {return new Machine(id, this.ctx)} 88 | 89 | 90 | get_direct_id() { 91 | let mach = this.get_direct() 92 | if (mach) return mach.get_id() 93 | } 94 | 95 | 96 | get_direct() {return this.machines['__direct__']} 97 | 98 | 99 | get_direct_config(group) { 100 | let mach = this.get_direct() 101 | return mach ? mach.get_config(group) : {} 102 | } 103 | 104 | 105 | *get_units() { 106 | let found = {} 107 | 108 | for (let mach of this) { 109 | let units = (mach.get_data().wus || []).concat(mach.get_units()) 110 | 111 | for (let unit of units) { 112 | if (!(unit instanceof Unit)) unit = new Unit(this.ctx, unit, mach) 113 | 114 | if (unit.id && unit.project && !found[unit.id]) { 115 | found[unit.id] = true 116 | yield unit 117 | } 118 | } 119 | } 120 | } 121 | 122 | 123 | get_unit(id) { 124 | for (let unit of this.get_units()) 125 | if (unit.id == id) return unit 126 | return {} 127 | } 128 | 129 | 130 | active_unit_sum(fn) { 131 | return Array.from(this).reduce((sum, mach) => { 132 | if (!mach.is_recently_connected) return sum 133 | 134 | return mach.get_units().reduce((sum, unit) => { 135 | if (unit.state != 'RUN' && !unit.finish) return sum 136 | let value = fn(unit) 137 | return sum + (isFinite(value) ? value : 0) 138 | }, sum) 139 | }, 0) 140 | } 141 | 142 | 143 | get ppd() {return this.active_unit_sum(unit => unit.unit.ppd)} 144 | 145 | 146 | async set_state(state) { 147 | await this.ctx.$node.broadcast('state', {state}) 148 | 149 | for (let mach of this) 150 | if (mach.is_direct()) 151 | await mach.set_state(state) 152 | } 153 | 154 | 155 | wus_enable(enable) { 156 | for (let mach of this) 157 | mach.wus_enable(enable) 158 | 159 | this.wus_enabled = enable 160 | } 161 | } 162 | 163 | export default Machines 164 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************\ 2 | 3 | This file is part of the Folding@home Client. 4 | 5 | The fah-client runs Folding@home protein folding simulations. 6 | Copyright (c) 2001-2024, foldingathome.org 7 | All rights reserved. 8 | 9 | This program is free software; you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation; either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License along 20 | with this program; if not, write to the Free Software Foundation, Inc., 21 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 22 | 23 | For information regarding this software email: 24 | Joseph Coffland 25 | joseph@cauldrondevelopment.com 26 | 27 | \******************************************************************************/ 28 | 29 | import {createApp} from 'vue' 30 | import App from './App.vue' 31 | import router from './router' 32 | import Button from './Button.vue' 33 | import Dialog from './Dialog.vue' 34 | import ProgressBar from './ProgressBar.vue' 35 | import DragList from './DragList.vue' 36 | import InfoItem from './InfoItem.vue' 37 | import Award from './Award.vue' 38 | import HelpBalloon from './HelpBalloon.vue' 39 | import ImageInput from './ImageInput.vue' 40 | import PlotView from './PlotView.vue' 41 | import FAHLogo from './FAHLogo.vue' 42 | import ClientVersion from './ClientVersion.vue' 43 | import ViewHeader from './ViewHeader.vue' 44 | import MainHeader from './MainHeader.vue' 45 | import ProjectView from './ProjectView.vue' 46 | import UnitHeader from './UnitHeader.vue' 47 | import UnitHeaders from './UnitHeaders.vue' 48 | import UnitsView from './UnitsView.vue' 49 | import UnitField from './UnitField.vue' 50 | import UnitInfo from './UnitInfo.vue' 51 | import Cache from './cache.js' 52 | import API from './api.js' 53 | import APISock from './api-sock.js' 54 | import Account from './account.js' 55 | import Util from './util.js' 56 | import Crypto from './crypto.js' 57 | import Node from './node.js' 58 | import Machines from './machines.js' 59 | import Stats from './stats.js' 60 | import Projects from './projects.js' 61 | import News from './news.js' 62 | import DirectMachConn from './direct-mach-conn.js' 63 | 64 | 65 | function add_components(app, components) { 66 | for (let [name, component] of Object.entries(components)) 67 | app.component(name, component) 68 | } 69 | 70 | 71 | async function main(url) { 72 | const app = createApp(App); 73 | const ctx = app.config.globalProperties 74 | ctx.$ctx = ctx 75 | ctx.$util = new Util 76 | ctx.$crypto = new Crypto(ctx) 77 | ctx.$cache = new Cache('fah') 78 | ctx.$api = new API(ctx, url) 79 | ctx.$apiSock = new APISock(ctx, 'https://ws.foldingathome.org/') 80 | ctx.$account = new Account(ctx) 81 | ctx.$adata = await ctx.$account.try_login() 82 | ctx.$machs = new Machines(ctx) 83 | ctx.$node = new Node(ctx) 84 | ctx.$projects = new Projects(ctx) 85 | ctx.$stats = new Stats(ctx) 86 | ctx.$news = new News(ctx) 87 | 88 | let addr = ctx.$util.get_direct_address() 89 | ctx.$direct = new DirectMachConn(ctx, 'local', addr) 90 | 91 | console.debug({account: Object.assign({}, ctx.$adata)}) 92 | 93 | app.use(router) 94 | add_components(app, { 95 | Button, Dialog, ProgressBar, Award, HelpBalloon, FAHLogo, ClientVersion, 96 | ViewHeader, MainHeader, ProjectView, InfoItem, DragList, UnitHeader, 97 | UnitsView, ImageInput, PlotView, UnitField, UnitHeaders, UnitInfo 98 | }) 99 | 100 | app.mount('#app') 101 | } 102 | 103 | 104 | console.debug('Web Control Version', import.meta.env.PACKAGE_VERSION) 105 | main(import.meta.env.VITE_API_URL || 'https://api.foldingathome.org') 106 | -------------------------------------------------------------------------------- /src/matrix.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | This file is part of the Folding@home Client. 3 | 4 | The fah-client runs Folding@home protein folding simulations. 5 | Copyright (c) 2001-2024, foldingathome.org 6 | All rights reserved. 7 | 8 | This program is free software; you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation; either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License along 19 | with this program; if not, write to the Free Software Foundation, Inc., 20 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 21 | 22 | For information regarding this software email: 23 | Joseph Coffland 24 | joseph@cauldrondevelopment.com 25 | *******************************************************************************/ 26 | 27 | class Matrix { 28 | constructor(m = [1, 0, 0, 0, 1, 0, 0, 0, 1]) {this.m = m} 29 | 30 | 31 | clone() {return new Matrix(this.m)} 32 | 33 | 34 | mul2d(v) { 35 | return { 36 | x: this.m[0] * v.x + this.m[1] * v.y + this.m[2], 37 | y: this.m[3] * v.x + this.m[4] * v.y + this.m[5], 38 | } 39 | } 40 | 41 | 42 | multiply(m) { 43 | const a = this.m 44 | const b = m instanceof Matrix ? m.m : m 45 | 46 | this.m = [ 47 | a[0] * b[0] + a[1] * b[3] + a[2] * b[6], 48 | a[0] * b[1] + a[1] * b[4] + a[2] * b[7], 49 | a[0] * b[2] + a[1] * b[5] + a[2] * b[8], 50 | a[3] * b[0] + a[4] * b[3] + a[5] * b[6], 51 | a[3] * b[1] + a[4] * b[4] + a[5] * b[7], 52 | a[3] * b[2] + a[4] * b[5] + a[5] * b[8], 53 | a[6] * b[0] + a[7] * b[3] + a[8] * b[6], 54 | a[6] * b[1] + a[7] * b[4] + a[8] * b[7], 55 | a[6] * b[2] + a[7] * b[5] + a[8] * b[8], 56 | ] 57 | } 58 | 59 | 60 | rotate(a) { 61 | const c = cos(n), s = sin(n) 62 | this.multiply([c, -s, 0, s, c, 0, 0, 0, 1]) 63 | } 64 | 65 | 66 | translate(x = 0, y = 0) {this.multiply([1, 0, x, 0, 1, y, 0, 0, 1])} 67 | scale (x = 1, y = 1) {this.multiply([x, 0, 0, 0, y, 0, 0, 0, 1])} 68 | 69 | 70 | inverse() { 71 | let m = this.m 72 | 73 | let b = [ 74 | m[8] * m[4] - m[5] * m[7], 75 | -m[8] * m[1] + m[2] * m[7], 76 | m[5] * m[1] - m[2] * m[4], 77 | -m[8] * m[3] + m[5] * m[6], 78 | m[8] * m[0] - m[2] * m[6], 79 | -m[5] * m[0] + m[2] * m[3], 80 | m[7] * m[3] - m[4] * m[6], 81 | -m[7] * m[0] + m[1] * m[6], 82 | m[4] * m[0] - m[1] * m[3], 83 | ] 84 | 85 | let det = m[0] * b[0] + m[1] * b[3] + m[2] * b[6] 86 | if (!det) return 87 | det = 1.0 / det 88 | 89 | return new Matrix(b.map(x => x * det)) 90 | } 91 | } 92 | 93 | export default Matrix 94 | -------------------------------------------------------------------------------- /src/news.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************\ 2 | 3 | This file is part of the Folding@home Client. 4 | 5 | The fah-client runs Folding@home protein folding simulations. 6 | Copyright (c) 2001-2024, foldingathome.org 7 | All rights reserved. 8 | 9 | This program is free software; you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation; either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License along 20 | with this program; if not, write to the Free Software Foundation, Inc., 21 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 22 | 23 | For information regarding this software email: 24 | Joseph Coffland 25 | joseph@cauldrondevelopment.com 26 | 27 | \******************************************************************************/ 28 | 29 | import {reactive} from 'vue' 30 | 31 | 32 | class News { 33 | constructor(ctx, url = 'https://foldingathome.org/wp-json/wp/v2', 34 | timeout = 24 * 60 * 60 * 1000) { 35 | this.cache = ctx.$cache 36 | this.url = url 37 | this.timeout = timeout 38 | this.data = reactive({ 39 | authors: {}, 40 | feed: [], 41 | }) 42 | 43 | this._update() 44 | } 45 | 46 | 47 | get_feed() {return this.data.feed} 48 | 49 | 50 | set_feed(feed) { 51 | feed = feed.slice() // Copy 52 | let result = [] 53 | 54 | while (feed.length) { 55 | // Choose next article giving newer articles a higher probability 56 | let r = Math.random() 57 | let x = Math.floor(-Math.log(r) / Math.log(3 / 2)) 58 | let i = (r == 0 || feed.length <= x) ? 0 : x 59 | 60 | result.push(feed[i]) 61 | feed.splice(i, 1) 62 | } 63 | 64 | this.data.feed = result 65 | } 66 | 67 | 68 | async get_featured_image(article, post) { 69 | let url = `${this.url}/media/${post.featured_media}?context=embed` 70 | let r = await fetch(url) 71 | let media = await r.json() 72 | let details = media.media_details || {} 73 | article.image = ((details.sizes || {}).medium || {}).source_url 74 | } 75 | 76 | 77 | async get_author(article, post) { 78 | if (post.author in this.data.authors) { 79 | article.author = this.data.authors[post.author] 80 | return 81 | } 82 | 83 | let r = await fetch(`${this.url}/users/${post.author}`) 84 | let author = await r.json() 85 | this.data.authors[post.author] = author.name 86 | article.author = author.name 87 | } 88 | 89 | 90 | async _update() { 91 | try { 92 | await this._load_feed() 93 | } catch (e) {console.log(e)} 94 | 95 | setTimeout(() => this._update(), 60 * 60 * 1000) 96 | } 97 | 98 | 99 | async _load_feed() { 100 | // Check cache 101 | let data = await this.cache.get('news', this.timeout) 102 | if (data) return this.set_feed(data) 103 | 104 | // Download feed 105 | let r = await fetch(`${this.url}/posts?context=embed`) 106 | let posts = await r.json() 107 | 108 | let feed = [] 109 | let promises = [] 110 | 111 | for (const post of posts) { 112 | let desc = post.excerpt.rendered 113 | .replace('>Read more<', 'target="_blank">Read more<') 114 | 115 | let article = reactive({ 116 | url: post.link, 117 | title: post.title.rendered, 118 | date: new Date(post.date).toDateString(), 119 | description: desc 120 | }) 121 | feed.push(article) 122 | 123 | promises.push(this.get_featured_image(article, post)) 124 | promises.push(this.get_author(article, post)) 125 | } 126 | 127 | if (!feed.length) return 128 | this.set_feed(feed) 129 | 130 | // Cache results 131 | await Promise.all(promises) 132 | await this.cache.set('news', feed) 133 | } 134 | } 135 | 136 | export default News 137 | -------------------------------------------------------------------------------- /src/node-mach-conn.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************\ 2 | 3 | This file is part of the Folding@home Client. 4 | 5 | The fah-client runs Folding@home protein folding simulations. 6 | Copyright (c) 2001-2024, foldingathome.org 7 | All rights reserved. 8 | 9 | This program is free software; you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation; either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License along 20 | with this program; if not, write to the Free Software Foundation, Inc., 21 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 22 | 23 | For information regarding this software email: 24 | Joseph Coffland 25 | joseph@cauldrondevelopment.com 26 | 27 | \******************************************************************************/ 28 | 29 | import MachConnection from './mach-connection.js' 30 | 31 | 32 | class NodeMachConn extends MachConnection { 33 | constructor(ctx, mach, key) { 34 | super(mach) 35 | this.ctx = ctx 36 | this.key = key 37 | this.ivs = {} 38 | } 39 | 40 | 41 | async open() { 42 | await this._send({ 43 | type: 'session-open', 44 | session: this.ctx.$node.sid, 45 | }) 46 | 47 | this.on_open() 48 | } 49 | 50 | 51 | close() {this.on_close()} 52 | 53 | 54 | // From MachConnection 55 | is_connected() {return true} 56 | 57 | 58 | async send(msg) { 59 | return this._send({ 60 | type: 'message', 61 | session: this.ctx.$node.sid, 62 | content: msg, 63 | }) 64 | } 65 | 66 | 67 | async _send(msg) { 68 | console.debug('Sending:', msg) 69 | 70 | let iv = this.ctx.$crypto.get_random(16) 71 | 72 | let payload = JSON.stringify(msg) 73 | payload = await this.ctx.$crypto.aes(this.key, iv, payload, true) 74 | payload = this.ctx.$util.urlbase64_encode(payload) 75 | 76 | iv = this.ctx.$util.urlbase64_encode(iv) 77 | this.ivs[iv] = true 78 | 79 | msg = {type: 'message', id: this.get_id(), iv, payload} 80 | return this.ctx.$node.send(msg) 81 | } 82 | 83 | 84 | async receive(msg) { 85 | // Check that this is a new IV. Also prevents replay attacks. 86 | let iv = msg.iv 87 | if (this.ivs[iv]) throw 'IV cannot be used more than once' 88 | if (1e6 < this.ivs.length) throw 'Too many IVs' 89 | this.ivs[iv] = true 90 | iv = this.ctx.$util.base64_decode(iv) 91 | 92 | let payload = this.ctx.$util.base64_decode(msg.payload) 93 | payload = await this.ctx.$crypto.aes(this.key, iv, payload, false) 94 | 95 | // Decompress 96 | if (msg.compression) 97 | payload = await this.ctx.$util.decompress(payload, msg.compression) 98 | 99 | payload = JSON.parse(payload) 100 | 101 | if (payload.session != this.ctx.$node.sid) 102 | throw 'Message not for this session' 103 | 104 | // TODO find correct machine instance for payload.group 105 | 106 | // Process message content 107 | this.on_message(payload.content) 108 | } 109 | } 110 | 111 | 112 | export default NodeMachConn 113 | -------------------------------------------------------------------------------- /src/projects.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************\ 2 | 3 | This file is part of the Folding@home Client. 4 | 5 | The fah-client runs Folding@home protein folding simulations. 6 | Copyright (c) 2001-2024, foldingathome.org 7 | All rights reserved. 8 | 9 | This program is free software; you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation; either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License along 20 | with this program; if not, write to the Free Software Foundation, Inc., 21 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 22 | 23 | For information regarding this software email: 24 | Joseph Coffland 25 | joseph@cauldrondevelopment.com 26 | 27 | \******************************************************************************/ 28 | 29 | import {watchEffect, reactive} from 'vue' 30 | 31 | 32 | class Projects { 33 | constructor(ctx, timeout = 24 * 60 * 60 * 1000) { 34 | this.ctx = ctx 35 | this.timeout = timeout 36 | this.state = reactive({ 37 | loading: true, 38 | in_progress: {}, 39 | projects: {}, 40 | }) 41 | 42 | watchEffect(() => this._update_ids()) 43 | setTimeout(() => this.state.loading = false, 8000) 44 | } 45 | 46 | 47 | is_loading() {return this.state.loading} 48 | 49 | 50 | get(id) { 51 | if (id) return this.state.projects[id] 52 | return Object.values(this.state.projects) 53 | } 54 | 55 | 56 | _update_ids() { 57 | let projects = {} 58 | 59 | for (let unit of this.ctx.$machs.get_units()) 60 | if (unit.assign.project) projects[unit.project] = true 61 | 62 | projects = Object.keys(projects) 63 | 64 | if (projects != this.state.ids) { 65 | this.state.ids = projects 66 | this._trigger_update() 67 | } 68 | } 69 | 70 | 71 | _trigger_update() { 72 | if (this.update_timer == undefined) 73 | this.update_timer = setTimeout(() => this._update(), 1000) 74 | } 75 | 76 | 77 | async _update() { 78 | delete this.update_timer 79 | 80 | // Load project data 81 | for (let id of this.state.ids) 82 | try {await this._load(id)} catch(e) {} 83 | 84 | // Remove old projects 85 | for (let id of Object.keys(this.state.projects)) 86 | if (!id in this.state.ids) delete this.state.projects[id] 87 | } 88 | 89 | 90 | async _load(id) { 91 | if (this.state.projects[id] || this.state.in_progress[id]) return 92 | 93 | this.state.in_progress[id] = true 94 | try { 95 | let url = this.ctx.$api.url + '/project/' + id 96 | let data = await this.ctx.$api.fetch({ 97 | path: '/project/' + id, expire: 0, 98 | action: 'Downloading project description.', 99 | error_cb: () => false // Don't show error message 100 | }) 101 | 102 | if (!data.error) { 103 | data.id = parseInt(id) 104 | this.state.projects[id] = data 105 | } 106 | 107 | } finally {this.state.in_progress[id] = false} 108 | } 109 | } 110 | 111 | export default Projects 112 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************\ 2 | 3 | This file is part of the Folding@home Client. 4 | 5 | The fah-client runs Folding@home protein folding simulations. 6 | Copyright (c) 2001-2024, foldingathome.org 7 | All rights reserved. 8 | 9 | This program is free software; you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation; either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License along 20 | with this program; if not, write to the Free Software Foundation, Inc., 21 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 22 | 23 | For information regarding this software email: 24 | Joseph Coffland 25 | joseph@cauldrondevelopment.com 26 | 27 | \******************************************************************************/ 28 | 29 | import {createRouter, createWebHistory} from 'vue-router' 30 | import StatsView from './StatsView.vue' 31 | import MachinesView from './MachinesView.vue' 32 | import NewsView from './NewsView.vue' 33 | import ProjectsView from './ProjectsView.vue' 34 | import WUsView from './WUsView.vue' 35 | import SettingsView from './SettingsView.vue' 36 | import Visualization from './Visualization.vue' 37 | import LogView from './LogView.vue' 38 | import MachineMux from './MachineMux.vue' 39 | import MachineDetailsView from './MachineDetailsView.vue' 40 | import UnitDetailsView from './UnitDetailsView.vue' 41 | import AccountView from './AccountView.vue' 42 | import VerifyView from './VerifyView.vue' 43 | import ResetView from './ResetView.vue' 44 | import AccountSettings from './AccountSettings.vue' 45 | import AccountAppearance from './AccountAppearance.vue' 46 | import AccountTeams from './AccountTeams.vue' 47 | 48 | 49 | export default createRouter({ 50 | history: createWebHistory(), 51 | routes: [ 52 | {path: '/', redirect: '/machines'}, 53 | {path: '/stats', component: StatsView}, 54 | {path: '/machines', component: MachinesView}, 55 | {path: '/projects', component: ProjectsView}, 56 | {path: '/wus', component: WUsView}, 57 | {path: '/news', component: NewsView}, 58 | {path: '/unit/:unitID', component: UnitDetailsView, props: true}, 59 | {path: '/verify/:token', component: VerifyView, props: true}, 60 | { 61 | path: '/account/:tab?', 62 | component: AccountView, 63 | props: route => route.params, 64 | children: [ 65 | {path: 'settings', component: AccountSettings}, 66 | {path: 'appearance', component: AccountAppearance}, 67 | {path: 'teams', component: AccountTeams}, 68 | {path: ':pathMatch(.*)', redirect: '/account/settings'}, 69 | ] 70 | }, { 71 | path: '/reset/:token', 72 | component: ResetView, 73 | props: route => Object.assign({email: route.query.email}, route.params) 74 | }, { 75 | path: '/:machID?', 76 | props: true, 77 | component: MachineMux, 78 | children: [ 79 | {path: '', redirect: '/'}, 80 | {path: 'settings', component: SettingsView}, 81 | {path: 'details', component: MachineDetailsView}, 82 | {path: 'view/:unitID', component: Visualization, props: true}, 83 | { 84 | path: 'log', 85 | component: LogView, 86 | props: route => ({query: route.query.q}) 87 | }, 88 | {path: ':pathMatch(.*)*', redirect: '/'}, 89 | ] 90 | }, 91 | ] 92 | }) 93 | -------------------------------------------------------------------------------- /src/sock.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************\ 2 | 3 | This file is part of the Folding@home Client. 4 | 5 | The fah-client runs Folding@home protein folding simulations. 6 | Copyright (c) 2001-2024, foldingathome.org 7 | All rights reserved. 8 | 9 | This program is free software; you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation; either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License along 20 | with this program; if not, write to the Free Software Foundation, Inc., 21 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 22 | 23 | For information regarding this software email: 24 | Joseph Coffland 25 | joseph@cauldrondevelopment.com 26 | 27 | \******************************************************************************/ 28 | 29 | class Sock { 30 | constructor(url, timeout = 20000) { 31 | this.url = url 32 | this.timeout = timeout 33 | this.connected = false 34 | } 35 | 36 | 37 | set_url(url) {this.url = url} 38 | set_timeout(timeout) {this.timeout = timeout} 39 | 40 | 41 | on_message(msg) {console.log('WS:', msg)} 42 | on_open(event) {} 43 | on_close(event) {} 44 | on_error(event) {} 45 | 46 | 47 | _clear_timeout() {clearTimeout(this.timer)} 48 | 49 | 50 | _open(event) { 51 | this.connected = true 52 | this._clear_timeout() 53 | this.on_open(event) 54 | } 55 | 56 | 57 | _close(event) { 58 | this._clear_timeout() 59 | this.connected = false 60 | this.ws = undefined 61 | this.on_close(event) 62 | } 63 | 64 | 65 | _error(event) {this.on_error(event)} 66 | _message(event) {this.on_message(JSON.parse(event.data))} 67 | _timeout() {this.close()} 68 | 69 | 70 | close() { 71 | if (this.ws) this.ws.close() 72 | this._clear_timeout() 73 | } 74 | 75 | 76 | connect() { 77 | if (this.ws != undefined) return 78 | 79 | console.debug('Connecting to ' + this.url) 80 | 81 | this.ws = new WebSocket(this.url) 82 | 83 | this.ws.onopen = e => this._open(e) 84 | this.ws.onclose = e => this._close(e) 85 | this.ws.onerror = e => this._error(e) 86 | this.ws.onmessage = e => this._message(e) 87 | 88 | this.timer = setTimeout(() => this._timeout(), this.timeout) 89 | } 90 | 91 | 92 | send(msg) { 93 | if (this.connected) this.ws.send(JSON.stringify(msg)) 94 | else console.debug('Cannot send message, not connected:', msg) 95 | } 96 | } 97 | 98 | 99 | export default Sock 100 | -------------------------------------------------------------------------------- /src/stats.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************\ 2 | 3 | This file is part of the Folding@home Client. 4 | 5 | The fah-client runs Folding@home protein folding simulations. 6 | Copyright (c) 2001-2024, foldingathome.org 7 | All rights reserved. 8 | 9 | This program is free software; you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation; either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License along 20 | with this program; if not, write to the Free Software Foundation, Inc., 21 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 22 | 23 | For information regarding this software email: 24 | Joseph Coffland 25 | joseph@cauldrondevelopment.com 26 | 27 | \******************************************************************************/ 28 | 29 | import {watch, watchEffect, reactive} from 'vue' 30 | 31 | 32 | class Stats { 33 | constructor(ctx, timeout = 60 * 60 * 1000) { 34 | this.ctx = ctx 35 | this.api = ctx.$api 36 | this.adata = ctx.$account.data 37 | this.machs = ctx.$machs 38 | this.state = reactive({ 39 | user: undefined, 40 | team: undefined, 41 | passkey: undefined, 42 | stats: {} 43 | }) 44 | this.timeout = timeout 45 | this.url = 'https://stats.foldingathome.org' 46 | 47 | watch([ 48 | () => this.state.user, 49 | () => this.state.team, 50 | () => this.state.passkey 51 | ], () => this._get_stats()) 52 | 53 | watchEffect(() => this._update_config()) 54 | 55 | this._update() 56 | } 57 | 58 | 59 | get_data() {return this.state.stats} 60 | 61 | 62 | get_team() { 63 | let ateam 64 | for (let team of (this.adata.teams || [])) 65 | if (team.team == this.state.team) ateam = team 66 | 67 | for (let team of this.state.stats.teams || []) 68 | if (team.team == this.state.team) { 69 | if (ateam != undefined) return Object.assign({}, team, ateam) 70 | return team 71 | } 72 | 73 | return {} 74 | } 75 | 76 | 77 | get charts() { 78 | let charts = [] 79 | 80 | let team = this.state.team 81 | if (team) charts.push({type: 'team', team}) 82 | 83 | let uid = this.state.stats.id 84 | let pid = this.state.stats.pid || 0 85 | if (uid) charts.push({type: 'user', uid, pid, user: this.state.user}) 86 | 87 | return charts 88 | } 89 | 90 | 91 | is_anon() { 92 | let user = this.state.user 93 | return !user || user.toLowerCase() == 'anonymous' 94 | } 95 | 96 | 97 | _update() { 98 | // Update stats periodically (cached up to `timeout`) 99 | setTimeout(() => this._update(), 60 * 1000) 100 | this._get_stats() 101 | } 102 | 103 | 104 | _get_config() { 105 | // Use account settings 106 | if (this.ctx.$account.logged_in) return this.adata 107 | 108 | // Otherwise use direct machine settings 109 | return this.machs.get_direct_config() 110 | } 111 | 112 | 113 | _update_config() { 114 | let {user, team, passkey} = this._get_config() 115 | 116 | this.state.user = user 117 | this.state.team = team 118 | this.state.passkey = passkey 119 | } 120 | 121 | 122 | async _get_team_stats(team) { 123 | let data = await this.api.fetch({ 124 | path: `/team/${team}`, error_cb: () => false, expire: this.timeout}) 125 | 126 | if (data && data.id != undefined) 127 | return Object.assign(data, { 128 | team, 129 | tscore: data.score, 130 | twus: data.wus, 131 | score: 0, 132 | wus: 0, 133 | }) 134 | 135 | return {team, name: team, tscore: 0, twus: 0, score: 0, wus: 0} 136 | } 137 | 138 | 139 | async _get_stats() { 140 | let {user, team, passkey} = this.state 141 | 142 | if (this.is_anon() && !team) return this.state.stats = {} 143 | 144 | let path = `/user/${encodeURIComponent(user)}` 145 | let data = team == undefined ? {} : {team} 146 | if (this.state.passkey) data.passkey = this.state.passkey 147 | 148 | let stats = await this.api.fetch({ 149 | path, data, error_cb: () => false, expire: this.timeout}) 150 | if (stats && stats.name) this.state.stats = stats 151 | 152 | if (!this.state.stats) { 153 | this.state.stats = { 154 | name: user, id: 0, score: 0, wus: 0, active_7: 0, active_50: 0, 155 | teams: [await this._get_team_stats(team)] 156 | } 157 | } 158 | } 159 | } 160 | 161 | 162 | export default Stats 163 | -------------------------------------------------------------------------------- /src/subscriber.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************\ 2 | 3 | This file is part of the Folding@home Client. 4 | 5 | The fah-client runs Folding@home protein folding simulations. 6 | Copyright (c) 2001-2025, foldingathome.org 7 | All rights reserved. 8 | 9 | This program is free software; you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation; either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License along 20 | with this program; if not, write to the Free Software Foundation, Inc., 21 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 22 | 23 | For information regarding this software 358,797,681email: 24 | Joseph Coffland 25 | joseph@cauldrondevelopment.com 26 | 27 | \******************************************************************************/ 28 | 29 | class Subscriber { 30 | constructor(sock, max_count = 10000, period = 3600) { 31 | this.sock = sock 32 | this.subscribers = {} 33 | this.data = [] 34 | this.max_count = max_count 35 | this.period = period 36 | this.msg = {} 37 | } 38 | 39 | 40 | get has_subscribers() {return 0 < Object.keys(this.subscribers).length} 41 | 42 | 43 | add_subscriber(cb) { 44 | let id = this.sock.nextID++ 45 | this.subscribers[id] = cb 46 | this.data.map(cb) 47 | this.update() 48 | return id 49 | } 50 | 51 | 52 | del_subscriber(id) { 53 | delete this.subscribers[id] 54 | this.update() 55 | } 56 | 57 | 58 | _get_message(type) {return Object.assign({type}, this.msg)} 59 | 60 | 61 | _cache_get_key(entry) {return '/?ts=' + new Date(entry.time).getTime()} 62 | 63 | 64 | async _cache_add(entry) { 65 | let res = new Response(JSON.stringify(entry)) 66 | return this.cache.put(this._cache_get_key(entry), res) 67 | } 68 | 69 | 70 | async _cache_del(entry) { 71 | return this.cache.delete(this._cache_get_key(entry)) 72 | } 73 | 74 | 75 | async _cache_load() { 76 | if (this.cache) return 77 | this.cache = await caches.open('fah-' + this.ref) 78 | 79 | let data = [] 80 | let responses = await this.cache.matchAll('/', {ignoreSearch: true}) 81 | for (let res of responses) { 82 | let entry = await res.json() 83 | let ts = new Date(entry.time).getTime() 84 | data.push([ts, entry]) 85 | } 86 | 87 | // Sort the data descending in time 88 | data.sort((a, b) => b[0] < a[0]) 89 | this.data = data.map(e => e[1]) 90 | this._limit_data() 91 | this._notify(this.data) 92 | } 93 | 94 | 95 | async _subscribe() { 96 | await this._cache_load() 97 | if (!this.sock.connected) return this.sock.connect() 98 | 99 | let msg = this._get_message('subscribe') 100 | msg.max_count = this.max_count 101 | if (this.data.length) msg.since = this.data[this.data.length - 1].time 102 | 103 | this.sock.send(msg) 104 | this.subscribed = true 105 | } 106 | 107 | 108 | _unsubscribe() { 109 | if (this.sock.connected) this.sock.send(this._get_message('unsubscribe')) 110 | this.subscribed = false 111 | } 112 | 113 | 114 | _notify(data) {Object.values(this.subscribers).map(cb => {data.map(cb)})} 115 | 116 | 117 | update() { 118 | if (!this.subscribed && this.has_subscribers) this._subscribe() 119 | if (this.subscribed && !this.has_subscribers) this._unsubscribe() 120 | } 121 | 122 | 123 | _limit_data() { 124 | if (this.max_count < this.data.length) { 125 | let removed = this.data.splice(0, this.data.length - this.max_count) 126 | removed.map(entry => this._cache_del(entry)) 127 | } 128 | } 129 | 130 | 131 | add_data(_data) { 132 | // Fill in missing data 133 | let data = [] 134 | let last = this.data[this.data.length - 1] 135 | 136 | for (let entry of _data) { 137 | if (last != undefined) { 138 | let lastTime = new Date( last.time).getTime() / 1000 139 | let thisTime = new Date(entry.time).getTime() / 1000 140 | let steps = Math.round((thisTime - lastTime) / this.period) 141 | 142 | if (steps < this.max_count) 143 | for (let i = 1; i < steps; i++) { 144 | let time = new Date((lastTime + this.period * i) * 1000) 145 | data.push({time: time.toISOString(), value: last.value}) 146 | } 147 | 148 | else { 149 | data = [] 150 | this.data = [] 151 | } 152 | } 153 | 154 | data.push(entry) 155 | last = entry 156 | } 157 | 158 | this._notify(data) 159 | this.data.push(...data) 160 | data.map(entry => this._cache_add(entry)) 161 | this._limit_data() 162 | } 163 | 164 | 165 | on_message(msg) { 166 | let data = Array.isArray(msg.data) ? msg.data.reverse() : [msg.data] 167 | this.add_data(data) 168 | } 169 | 170 | 171 | on_open() {this.update()} 172 | on_close() {this.subscribed = false} 173 | } 174 | 175 | 176 | export default Subscriber 177 | -------------------------------------------------------------------------------- /src/unit_fields.json: -------------------------------------------------------------------------------- 1 | { 2 | "Project": {"enabled": true, "minimal": true, "align": "left", 3 | "desc": "Project ID"}, 4 | "Type": {"enabled": true, "desc": "CPU or GPU"}, 5 | "CPUs": {"desc": "Number of CPUs in use"}, 6 | "GPUs": {"desc": "Number of GPUs in use"}, 7 | "GPUs Text": {"desc": "A description of the GPUs in use", 8 | "header": "GPUs"}, 9 | "Status": {"enabled": true, "minimal": true, "align": "center", 10 | "desc": "Color coded status icon"}, 11 | "Status Text": {"header": "Status", "align": "left", 12 | "desc": "Color coded status icon and text"}, 13 | "Progress": {"enabled": true, "minimal": true, "size": "minmax(6em, 20em)", 14 | "align": "left", "desc": "Percent complete progress bar"}, 15 | "PPD": {"enabled": true, "desc": "Estimated Points Per Day"}, 16 | "ETA": {"desc": "Estimated time to Work Unit completion"}, 17 | "TPF": {"desc": "Time Per Frame"}, 18 | "OS": {"desc": "Operating System icon"}, 19 | "OS Text": {"desc": "Operating System icon and name", "header": "OS"}, 20 | "Core": {"desc": "Folding core hexadecimal ID"}, 21 | "Deadline": {"desc": "Time until return deadline"}, 22 | "Timeout": {"desc": "Time until bonus timeout"}, 23 | "RCG": {"desc": "Run Clone Generation"}, 24 | "Run Time": {"desc": "Total Work Unit run time"}, 25 | "Base Credit": {"desc": "Work Unit base credit"}, 26 | "Assign Time": {"desc": "Work Unit assignment time in UTC"}, 27 | "Number": {"desc": "Work Unit number assigned by the machine it's on"}, 28 | "Machine": {"desc": "The name of the machine", "align": "left"}, 29 | "Work Server": {"desc": "Work Server that assigned the Work Unit"}, 30 | "Version": {"desc": "Client version"}, 31 | "Resources": {"desc": "CPU and GPU compute resources", "align": "left"}, 32 | "Group Name": {"desc": "Resource group name", "header": "Group", 33 | "align": "left"} 34 | } -------------------------------------------------------------------------------- /src/updatable.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************\ 2 | 3 | This file is part of the Folding@home Client. 4 | 5 | The fah-client runs Folding@home protein folding simulations. 6 | Copyright (c) 2001-2024, foldingathome.org 7 | All rights reserved. 8 | 9 | This program is free software; you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation; either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License along 20 | with this program; if not, write to the Free Software Foundation, Inc., 21 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 22 | 23 | For information regarding this software email: 24 | Joseph Coffland 25 | joseph@cauldrondevelopment.com 26 | 27 | \******************************************************************************/ 28 | 29 | 30 | function is_object(o) {return o != null && typeof o === 'object'} 31 | 32 | 33 | class Updatable { 34 | constructor(data) {Object.assign(this, Updatable.clean_keys(data))} 35 | 36 | 37 | static clean_key(key) { 38 | if (typeof key == 'string' && key.length <= 16) 39 | return key.replace('-', '_') 40 | return key 41 | } 42 | 43 | 44 | static clean_keys(data) { 45 | if (Array.isArray(data)) { 46 | let r = [] 47 | 48 | for (const value of data) 49 | r.push(Updatable.clean_keys(value)) 50 | 51 | return r 52 | } 53 | 54 | if (is_object(data)) { 55 | let r = {} 56 | 57 | for (const [key, value] of Object.entries(data)) 58 | r[Updatable.clean_key(key)] = Updatable.clean_keys(value) 59 | 60 | return r 61 | } 62 | 63 | return data 64 | } 65 | 66 | 67 | do_update(update) { 68 | let obj = this 69 | let i = 0 70 | 71 | while (i < update.length - 2) { 72 | let key = Updatable.clean_key(update[i++]) 73 | 74 | if (obj[key] == undefined) 75 | obj[key] = Number.isInteger(update[i]) ? [] : {} 76 | 77 | obj = obj[key] 78 | } 79 | 80 | let is_array = Array.isArray(obj) 81 | let key = Updatable.clean_key(update[i++]) 82 | let value = update[i] 83 | 84 | if (is_array && key === -1) obj.push(value) 85 | else if (is_array && key === -2) obj.splice(obj.length, 0, ...value) 86 | else if (is_array && value === null) obj.splice(key, 1) 87 | else if (value === null) delete obj[key] 88 | else obj[key] = value 89 | } 90 | } 91 | 92 | export default Updatable 93 | -------------------------------------------------------------------------------- /src/viewer/InfiniteGridHelper.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************\ 2 | 3 | This file is part of the Folding@home Client. 4 | 5 | The fah-client runs Folding@home protein folding simulations. 6 | Copyright (c) 2001-2024, foldingathome.org 7 | All rights reserved. 8 | 9 | This program is free software; you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation; either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License along 20 | with this program; if not, write to the Free Software Foundation, Inc., 21 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 22 | 23 | For information regarding this software email: 24 | Joseph Coffland 25 | joseph@cauldrondevelopment.com 26 | 27 | \******************************************************************************/ 28 | 29 | // Author: Fyrestar https://mevedia.com 30 | // (https://github.com/Fyrestar/THREE.InfiniteGridHelper) 31 | import * as THREE from 'three' 32 | import vertexShader from './grid.vert?raw' 33 | import fragmentShader from './grid.frag?raw' 34 | 35 | 36 | class InfiniteGridHelper extends THREE.Mesh { 37 | constructor(size1 = 10, size2 = 100, color = new THREE.Color('#888'), 38 | distance = 8000) { 39 | const matrix = 40 | new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0, 1, 0), 1) 41 | matrix.premultiply(new THREE.Matrix4().makeTranslation(0, -100, 0)) 42 | 43 | const geometry = new THREE.PlaneGeometry(2, 2, 1, 1) 44 | const material = new THREE.ShaderMaterial({ 45 | side: THREE.DoubleSide, 46 | transparent: true, 47 | vertexShader, 48 | fragmentShader, 49 | extensions: {derivatives: true}, 50 | uniforms: { 51 | uMatrix: new THREE.Uniform(matrix), 52 | uSize1: {value: size1}, 53 | uSize2: {value: size2}, 54 | uColor: {value: color}, 55 | uDistance: {value: distance} 56 | } 57 | }) 58 | 59 | super(geometry, material) 60 | this.frustumCulled = false 61 | } 62 | } 63 | 64 | export default InfiniteGridHelper 65 | -------------------------------------------------------------------------------- /src/viewer/Sky.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************\ 2 | 3 | This file is part of the Folding@home Client. 4 | 5 | The fah-client runs Folding@home protein folding simulations. 6 | Copyright (c) 2001-2024, foldingathome.org 7 | All rights reserved. 8 | 9 | This program is free software; you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation; either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License along 20 | with this program; if not, write to the Free Software Foundation, Inc., 21 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 22 | 23 | For information regarding this software email: 24 | Joseph Coffland 25 | joseph@cauldrondevelopment.com 26 | 27 | \******************************************************************************/ 28 | 29 | /** 30 | * @author zz85 / https://github.com/zz85 31 | * 32 | * Based on "A Practical Analytic Model for Daylight" 33 | * aka The Preetham Model, the de facto standard analytic skydome model 34 | * http://www.cs.utah.edu/~shirley/papers/sunsky/sunsky.pdf 35 | * 36 | * First implemented by Simon Wallner 37 | * http://www.simonwallner.at/projects/atmospheric-scattering 38 | * 39 | * Improved by Martin Upitis 40 | * http://blenderartists.org/forum/showthread.php? 41 | * 245954-preethams-sky-implementation-HDR 42 | * 43 | * Three.js integration by zz85 http://twitter.com/blurspline 44 | * 45 | * Node.js module implementation by Danila Loginov https://loginov.rocks 46 | */ 47 | import * as THREE from 'three' 48 | import vertexShader from './sky.vert?raw' 49 | import fragmentShader from './sky.frag?raw' 50 | 51 | 52 | class Sky extends THREE.Mesh { 53 | constructor() { 54 | const material = new THREE.ShaderMaterial({ 55 | side: THREE.BackSide, 56 | fragmentShader, 57 | vertexShader, 58 | uniforms: { 59 | luminance: {value: 1}, 60 | turbidity: {value: 10}, 61 | rayleigh: {value: 2}, 62 | mieCoefficient: {value: 0.005}, 63 | mieDirectionalG: {value: 0.8}, 64 | sunPosition: {value: new THREE.Vector3(-200, -3, -200)} 65 | } 66 | }) 67 | 68 | super(new THREE.SphereGeometry(5000, 320, 150), material) 69 | } 70 | } 71 | 72 | 73 | export default Sky 74 | -------------------------------------------------------------------------------- /src/viewer/grid.frag: -------------------------------------------------------------------------------- 1 | varying vec3 worldPosition; 2 | uniform float uSize1; 3 | uniform float uSize2; 4 | uniform vec3 uColor; 5 | uniform float uDistance; 6 | uniform mat4 uMatrix; 7 | 8 | 9 | float getGrid(float size) { 10 | vec2 r = worldPosition.xz / size; 11 | vec2 grid = abs(fract(r - 0.5) - 0.5) / fwidth(r); 12 | float line = min(grid.x, grid.y); 13 | 14 | return 1.0 - min(line, 1.0); 15 | } 16 | 17 | 18 | void main() { 19 | float d = 1.0 - min(distance(cameraPosition.xz, 20 | worldPosition.xz) / uDistance, 1.0); 21 | float g1 = getGrid(uSize1); 22 | float g2 = getGrid(uSize2); 23 | 24 | gl_FragColor = vec4(uColor.rgb, mix(g2, g1, g1) * pow(d, 3.0)); 25 | gl_FragColor.a = mix(0.5 * gl_FragColor.a, gl_FragColor.a, g2); 26 | 27 | if (gl_FragColor.a <= 0.0) discard; 28 | } 29 | -------------------------------------------------------------------------------- /src/viewer/grid.vert: -------------------------------------------------------------------------------- 1 | varying vec3 worldPosition; 2 | uniform float uDistance; 3 | uniform mat4 uMatrix; 4 | 5 | 6 | void main() { 7 | vec3 pos = position.xzy * uDistance; 8 | pos.xz += cameraPosition.xz; 9 | worldPosition = pos; 10 | gl_Position = projectionMatrix * modelViewMatrix * uMatrix * vec4(pos, 1.0); 11 | } 12 | -------------------------------------------------------------------------------- /src/viewer/sky.frag: -------------------------------------------------------------------------------- 1 | varying vec3 vWorldPosition; 2 | varying vec3 vSunDirection; 3 | varying float vSunfade; 4 | varying vec3 vBetaR; 5 | varying vec3 vBetaM; 6 | varying float vSunE; 7 | 8 | uniform float luminance; 9 | uniform float mieDirectionalG; 10 | 11 | const vec3 cameraPos = vec3(0.0, 0.0, 0.0); 12 | 13 | // constants for atmospheric scattering 14 | const float pi = 3.141592653589793238462643383279502884197169; 15 | 16 | const float n = 1.0003; // refractive index of air 17 | const float N = 2.545E25; // number of molecules per unit volume for air at 18 | // 288.15K and 1013mb (sea level -45 celsius) 19 | 20 | // optical length at zenith for molecules 21 | const float rayleighZenithLength = 8.4E3; 22 | const float mieZenithLength = 1.25E3; 23 | const vec3 up = vec3(0.0, 1.0, 0.0); 24 | 25 | // 66 arc seconds -> degrees, and the cosine of that 26 | const float sunAngularDiameterCos = 27 | 0.999956676946448443553574619906976478926848692873900859324; 28 | 29 | const float THREE_OVER_SIXTEENPI = 0.05968310365946075; // 3.0 / (16.0 * pi) 30 | const float ONE_OVER_FOURPI = 0.07957747154594767; // 1.0 / (4.0 * pi) 31 | 32 | 33 | float rayleighPhase(float cosTheta) { 34 | return THREE_OVER_SIXTEENPI * (1.0 + pow(cosTheta, 2.0)); 35 | } 36 | 37 | 38 | float hgPhase(float cosTheta, float g) { 39 | float g2 = pow(g, 2.0); 40 | float inverse = 1.0 / pow(1.0 - 2.0 * g * cosTheta + g2, 1.5); 41 | return ONE_OVER_FOURPI * ((1.0 - g2) * inverse); 42 | } 43 | 44 | 45 | // Filmic ToneMapping http://filmicgames.com/archives/75 46 | const float A = 0.15; 47 | const float B = 0.50; 48 | const float C = 0.10; 49 | const float D = 0.20; 50 | const float E = 0.02; 51 | const float F = 0.30; 52 | 53 | const float whiteScale = 1.0748724675633854; // 1.0 / Uncharted2Tonemap(1000.0) 54 | 55 | 56 | vec3 Uncharted2Tonemap(vec3 x) { 57 | return ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F; 58 | } 59 | 60 | 61 | void main() { 62 | // optical length 63 | // cutoff angle at 90 to avoid singularity in next formula. 64 | float zenithAngle = 65 | acos(max(0.0, dot(up, normalize(vWorldPosition - cameraPos)))); 66 | float inverse = 67 | 1.0 / (cos(zenithAngle) + 0.15 * 68 | pow(93.885 - ((zenithAngle * 180.0) / pi), -1.253)); 69 | float sR = rayleighZenithLength * inverse; 70 | float sM = mieZenithLength * inverse; 71 | 72 | // combined extinction factor 73 | vec3 Fex = exp(-(vBetaR * sR + vBetaM * sM)); 74 | 75 | // in scattering 76 | float cosTheta = dot(normalize(vWorldPosition - cameraPos), vSunDirection); 77 | 78 | float rPhase = rayleighPhase(cosTheta * 0.5 + 0.5); 79 | vec3 betaRTheta = vBetaR * rPhase; 80 | 81 | float mPhase = hgPhase(cosTheta, mieDirectionalG); 82 | vec3 betaMTheta = vBetaM * mPhase; 83 | 84 | vec3 Lin = pow(vSunE * ((betaRTheta + betaMTheta) / 85 | (vBetaR + vBetaM)) * (1.0 - Fex), vec3(1.5)); 86 | Lin *= mix(vec3(1.0), 87 | pow(vSunE * ((betaRTheta + betaMTheta) / (vBetaR + vBetaM)) * Fex, 88 | vec3(1.0 / 2.0)), 89 | clamp(pow(1.0 - dot(up, vSunDirection), 5.0), 0.0, 1.0)); 90 | 91 | // nightsky 92 | vec3 direction = normalize(vWorldPosition - cameraPos); 93 | float theta = acos(direction.y); // elevation --> y-axis, [-pi/2, pi/2] 94 | float phi = 95 | atan(direction.z, direction.x); // azimuth --> x-axis [-pi/2, pi/2] 96 | vec2 uv = vec2(phi, theta) / vec2(2.0 * pi, pi) + vec2(0.5, 0.0); 97 | vec3 L0 = vec3(0.1) * Fex; 98 | 99 | // composition + solar disc 100 | float sundisk = smoothstep(sunAngularDiameterCos, 101 | sunAngularDiameterCos + 0.00002, cosTheta); 102 | L0 += (vSunE * 19000.0 * Fex) * sundisk; 103 | 104 | vec3 texColor = (Lin + L0) * 0.04 + vec3(0.0, 0.0003, 0.00075); 105 | vec3 curr = Uncharted2Tonemap((log2(2.0 / pow(luminance, 4.0))) * texColor); 106 | vec3 color = curr * whiteScale; 107 | vec3 retColor = pow(color, vec3(1.0 / (1.2 + (1.2 * vSunfade)))); 108 | 109 | gl_FragColor = vec4(retColor, 1.0); 110 | } 111 | -------------------------------------------------------------------------------- /src/viewer/sky.vert: -------------------------------------------------------------------------------- 1 | uniform vec3 sunPosition; 2 | uniform float rayleigh; 3 | uniform float turbidity; 4 | uniform float mieCoefficient; 5 | 6 | varying vec3 vWorldPosition; 7 | varying vec3 vSunDirection; 8 | varying float vSunfade; 9 | varying vec3 vBetaR; 10 | varying vec3 vBetaM; 11 | varying float vSunE; 12 | 13 | const vec3 up = vec3(0.0, 1.0, 0.0); 14 | 15 | // constants for atmospheric scattering 16 | const float e = 2.71828182845904523536028747135266249775724709369995957; 17 | const float pi = 3.141592653589793238462643383279502884197169; 18 | 19 | // wavelength of used primaries, according to preetham 20 | const vec3 lambda = vec3(680E-9, 550E-9, 450E-9); 21 | 22 | // this pre-calculation replaces older TotalRayleigh(vec3 lambda) function: 23 | // (8.0 * pow(pi, 3.0) * pow(pow(n, 2.0) - 1.0, 2.0) * 24 | // (6.0 + 3.0 * pn)) / (3.0 * N * pow(lambda, vec3(4.0)) * (6.0 - 7.0 * pn)) 25 | const vec3 totalRayleigh = 26 | vec3(5.804542996261093E-6, 1.3562911419845635E-5, 3.0265902468824876E-5); 27 | 28 | // mie stuff 29 | // K coefficient for the primaries 30 | const float v = 4.0; 31 | const vec3 K = vec3(0.686, 0.678, 0.666); 32 | 33 | // MieConst = pi * pow((2.0 * pi) / lambda, vec3(v - 2.0)) * K 34 | const vec3 MieConst = 35 | vec3(1.8399918514433978E14, 2.7798023919660528E14, 4.0790479543861094E14); 36 | 37 | // earth shadow hack 38 | // cutoffAngle = pi / 1.95 39 | const float cutoffAngle = 1.6110731556870734; 40 | const float steepness = 1.5; 41 | const float EE = 1000.0; 42 | 43 | 44 | float sunIntensity(float zenithAngleCos) { 45 | zenithAngleCos = clamp(zenithAngleCos, -1.0, 1.0); 46 | return EE * max(0.0, 1.0 - 47 | pow(e, -((cutoffAngle - acos(zenithAngleCos)) / steepness))); 48 | } 49 | 50 | 51 | vec3 totalMie(float T) { 52 | float c = (0.2 * T) * 10E-18; 53 | return 0.434 * c * MieConst; 54 | } 55 | 56 | 57 | void main() { 58 | vec4 worldPosition = modelMatrix * vec4(position, 1.0); 59 | vWorldPosition = worldPosition.xyz; 60 | 61 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 62 | vSunDirection = normalize(sunPosition); 63 | vSunE = sunIntensity(dot(vSunDirection, up)); 64 | vSunfade = 1.0 - clamp(1.0 - exp((sunPosition.y / 450000.0)), 0.0, 1.0); 65 | 66 | float rayleighCoefficient = rayleigh - (1.0 * (1.0 - vSunfade)); 67 | 68 | // extinction (absorption + out scattering) rayleigh coefficients 69 | vBetaR = totalRayleigh * rayleighCoefficient; 70 | 71 | // mie coefficients 72 | vBetaM = totalMie(turbidity) * mieCoefficient; 73 | } 74 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig} from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import packageJson from './package.json' 4 | 5 | 6 | export default defineConfig({ 7 | preview: {port: 5173}, 8 | 9 | plugins: [ 10 | vue({ 11 | template: { 12 | compilerOptions: { 13 | isCustomElement: tag => ['tt', 'center'].includes(tag) 14 | } 15 | } 16 | }) 17 | ], 18 | 19 | define: { 20 | 'import.meta.env.PACKAGE_VERSION': JSON.stringify(packageJson.version) 21 | } 22 | }) 23 | --------------------------------------------------------------------------------