├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── channel_handler.go ├── channel_manager.go ├── daemon.go ├── doc └── rest_api.apib ├── fetch.sh ├── front_service_test.go ├── frontend_service.go ├── go.mod ├── go.sum ├── log_agent.go ├── log_manager.go ├── session_manager.go ├── user_manager.go └── web_root ├── apple-icon.png ├── asset-manifest.json ├── favicon.ico ├── index.html ├── manifest.json ├── precache-manifest.1aff4a0c2475d969f0bdc6822387a682.js ├── precache-manifest.4590baa3341561701ccab355dd1c9e60.js ├── precache-manifest.894bb590fa8ff419fafc579ff8334eb9.js ├── precache-manifest.f9bc75adcb5c0dff8eec578839242f95.js ├── service-worker.js └── static ├── css ├── 2.d0176e96.chunk.css ├── 2.d0176e96.chunk.css.map ├── main.13f8852b.chunk.css ├── main.13f8852b.chunk.css.map ├── main.92970355.chunk.css ├── main.92970355.chunk.css.map ├── main.dfd6ecea.chunk.css └── main.dfd6ecea.chunk.css.map ├── js ├── 2.0693f478.chunk.js ├── 2.0693f478.chunk.js.LICENSE.txt ├── 2.0693f478.chunk.js.map ├── 2.111defca.chunk.js ├── 2.111defca.chunk.js.LICENSE.txt ├── 2.111defca.chunk.js.map ├── 2.ba83a81c.chunk.js ├── 2.ba83a81c.chunk.js.LICENSE.txt ├── 2.ba83a81c.chunk.js.map ├── 2.d8260731.chunk.js ├── 2.d8260731.chunk.js.LICENSE.txt ├── 2.d8260731.chunk.js.map ├── main.2a00f055.chunk.js ├── main.2a00f055.chunk.js.LICENSE.txt ├── main.2a00f055.chunk.js.map ├── main.8156b384.chunk.js ├── main.8156b384.chunk.js.LICENSE.txt ├── main.8156b384.chunk.js.map ├── main.851f2e9f.chunk.js ├── main.851f2e9f.chunk.js.LICENSE.txt ├── main.851f2e9f.chunk.js.map ├── main.9fd6f9c0.chunk.js ├── main.9fd6f9c0.chunk.js.LICENSE.txt ├── main.9fd6f9c0.chunk.js.map ├── runtime-main.412f7afe.js ├── runtime-main.412f7afe.js.map ├── runtime-main.6ed7819b.js └── runtime-main.6ed7819b.js.map └── media ├── login_background.327add31.jpg ├── nano_white.a92c198a.svg ├── nano_white.ad11d364.svg └── sidebar.18c01f03.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | .idea 15 | config 16 | log 17 | pkg 18 | test 19 | frontend 20 | frontend.* 21 | 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [1.4.0] - 2023-11-07 4 | 5 | ### 新增 6 | 7 | - 配置文件"frontend.cfg"新增选项max_cores用于设置创建云主机的最大核心数,默认值24 8 | - 配置文件"frontend.cfg"新增选项max_memory用于设置创建云主机的最大内存容量(GB),默认值32 9 | - 配置文件"frontend.cfg"新增选项max_disk用于设置创建云主机的最大磁盘容量(GB),默认值64 10 | 11 | ### 变更 12 | 13 | - 更新依赖包及go版本 14 | - 新增front_service测试用例 15 | - 接口'GET: /system/'返回资源配额 16 | 17 | ### Added 18 | 19 | - Add option "max_cores" to configure file "frontend.cfg", default value is 24 20 | - Add option "max_memory" in GB to configure file "frontend.cfg", default value is 32 21 | - Add option "max_disk" in GB to configure file "frontend.cfg", default value is 64 22 | 23 | ### Changed 24 | 25 | - update to go 1.20 26 | - update dependent packages 27 | - Add test cases to front_service 28 | - Return max resource limit when get system status on path 'GET: /system/' 29 | 30 | ### Fixed 31 | 32 | - Search guests not filter by owner and group 33 | 34 | ## [1.3.1] - 2021-02-18 35 | 36 | ### Added 37 | 38 | - Search guests 39 | - Enable/Disable auto start option 40 | 41 | ## [1.3.0] - 2020-11-08 42 | 43 | ### Added 44 | 45 | - Forward request for sync images/security policy group 46 | 47 | ### Fixed 48 | 49 | - Return an explicit error when login with a user does not belong to any group 50 | - Remove from the group when deleting a user 51 | 52 | ## [1.2.0] - 2020-04-15 53 | 54 | ### Added 55 | 56 | - Query/Change storage path 57 | - Manage system templates 58 | - Create guest using template 59 | - Reset monitor secret 60 | 61 | ### Fixed 62 | 63 | - Huge memory occupied when uploading images cause OOM kill 64 | 65 | ## [1.1.3] - 2019-12-29 66 | 67 | ### Added 68 | 69 | - Add web_root option to "frontend.cfg" for hosting portal files 70 | - Add cors_enable option to "frontend.cfg" for CORS control 71 | 72 | ### Changed 73 | 74 | - A new portal completely was rewritten using React 75 | - Web pages move from 'resource' to 'web_root' 76 | 77 | ### Fixed 78 | 79 | - Visibility interface broken 80 | - Query logs missing and wrong order 81 | 82 | ## [1.1.2] - 2019-12-13 83 | 84 | ### Changed 85 | 86 | - Enable CORS request to monitor channel 87 | 88 | ## [1.1.0] - 2019-11-05 89 | 90 | ### Added 91 | 92 | - Add signature when call Core API 93 | - Add go mod 94 | 95 | ### Changed 96 | 97 | - Call core API via prefix '/api/v1/' 98 | - Change "/media_image_files/:id" to "/media_images/:id/file/" 99 | - Change "/disk_image_files/:id" to "/disk_images/:id/file/" 100 | 101 | ## [1.0.0] - 2019-07-14 102 | 103 | ### Added 104 | 105 | - Create guests with QoS options 106 | 107 | - Modify CPU priority in guest detail 108 | 109 | - Set threshold of Disk/Network IO in guest detail 110 | 111 | - Batch stop guests 112 | 113 | - Enable get/update group visibility 114 | 115 | - Record failed login 116 | 117 | ### Changed 118 | 119 | - URL of guest operates change from '/guest/' to '/guests/'. 120 | 121 | - Search guest/media/disk images via session 122 | 123 | ## [0.9.1] - 2019-05-27 124 | 125 | ### Added 126 | 127 | - Get media image 128 | 129 | - Query/Add/Remove operate log 130 | 131 | - Add version and online manual link to footer 132 | 133 | - Add new API "GET /media_image_search/" for filtering media images by owner and group 134 | 135 | - Modify disk/media images 136 | 137 | - Add operate logs in most pages 138 | 139 | ### Changed 140 | 141 | - Default landing page change to 'login.html' 142 | 143 | - Add group info in session 144 | 145 | - Bind resources to current user/group: Create instance/upload&build images 146 | 147 | - Media images filtered using the current user and group 148 | 149 | - Display the appropriate page according to the menu after login 150 | 151 | - Add padding space for sidenav 152 | 153 | - Move start/stop/restart/reset instance into nano.js 154 | 155 | ## [0.8.2] - 2019-04-4 156 | 157 | ### Fixed 158 | 159 | - Prompt an invalid dialog when removing ranges from an address pool 160 | 161 | - Logout when deleting address pool/computing pool/storage pool 162 | 163 | - Prompt an invalid dialog when removing a cell from computing pool 164 | 165 | - Logout when deleting a media/disk image 166 | 167 | - Logout when modify user/password 168 | 169 | - Logout when deleting user 170 | 171 | - Logout when removing role/group/group member 172 | 173 | ## [0.8.1] - 2019-03-21 174 | 175 | ### Added 176 | 177 | - Check resource path 178 | 179 | - Enable batch creating and deleting/modify guest name/modify image info 180 | 181 | - Batch creating/deleting guest 182 | 183 | - Modify user password 184 | 185 | - Modify guest name 186 | 187 | ### Changed 188 | 189 | - Locate resource using ABS path 190 | 191 | - Adapt to new runnable implement 192 | 193 | - Verify guest name before submit creating request 194 | 195 | - Navigation menu change to the sidebar 196 | 197 | - Update chartjs to v2.8 198 | 199 | ## [0.7.1] - 2018-12-11 200 | 201 | ### Added 202 | 203 | - Role/Group/User management 204 | 205 | - Session management 206 | 207 | - Invoke system initial page when no user configured 208 | 209 | - Enable reset system 210 | 211 | - Add "legacy system" option when create new instance 212 | 213 | - Add uptime to dashboard.html 214 | 215 | ## [0.6.1] - 2018-11-30 216 | 217 | ### Added 218 | 219 | - Redirect address pool/range API 220 | 221 | - Address pool/range management page 222 | 223 | ### Changed 224 | 225 | - Address pool option when creating/modifing compute pool 226 | 227 | ## [0.5.1] - 2018-11-3 228 | 229 | ### Added 230 | 231 | - Multi-language support 232 | 233 | - Enable/Disable cell 234 | 235 | - Enable failover option in compute pool 236 | 237 | - Migrate instance 238 | 239 | ### Changed 240 | 241 | - Optimize console output when starting module 242 | 243 | ## [0.4.1] - 2018-10-2 244 | 245 | ### Added 246 | 247 | - Support storage pool management 248 | 249 | - Modify compute pool 250 | 251 | - Get compute cell info 252 | 253 | - Standalone google materialize icons 254 | 255 | - Mark instance lost status when cell stop/disconnected 256 | 257 | - Add image id in list 258 | 259 | ### Fixed 260 | 261 | - No response to click start or insert media button when no media available. 262 | 263 | - Auto fresh page become slower after a long time running 264 | 265 | - Deleted instance count in the dashboard 266 | 267 | - Output message without clear previous info in the list 268 | 269 | ## [0.3.1] - 2018-8-29 270 | 271 | ### Added 272 | 273 | - Create time of Instance 274 | 275 | - Create/modify time of Image 276 | 277 | - Snapshot management: create/ delete / revert 278 | 279 | - Insert / eject media image 280 | 281 | ### Changed 282 | 283 | - Enable instance funtions in instance monitor page 284 | 285 | - Change cpu/memory usage to bar chart in dashboard/instance monitor 286 | 287 | - Open new tab for monitor/snapshots/details of instances list 288 | 289 | ## 2018-8-17 290 | 291 | ### Added 292 | 293 | - Support choose installed module when cloning from an image 294 | 295 | - Enable change admin password/ create new admin/ disk auto resize/ data disk mount when Cloud-Init module installed 296 | 297 | - Add auto fresh in instances.html 298 | 299 | ### Modified 300 | 301 | - Optimize instances/cells auto refresh, interval reduced to 5 seconds 302 | 303 | - Fixed: multiple image names displayed when starting with media 304 | 305 | ## 2018-8-7 306 | 307 | ### Modified 308 | 309 | - Display instance address/container cell in instances/instance_list 310 | 311 | - Display VNC address/secret in detail page 312 | 313 | ## 2018-8-6 314 | 315 | ### Modified 316 | 317 | - Add auto fresh switch in cell/instance list 318 | 319 | ## [0.2.1] - 2018-7-31 320 | 321 | ### Added 322 | 323 | - Doc: Modify core/memory/disk size, shrink disk, set/get password 324 | 325 | - Forward Request: Modify core/memory/disk size, shrink disk, set/get password 326 | 327 | - Add guest modify page: instance_detail.html 328 | 329 | ## [0.1.2] - 2018-7-25 330 | 331 | ### Modified 332 | 333 | - Version output 334 | 335 | - Disk image upload and download 336 | 337 | - Add auto refresh to compute_cells.html and instance_list.html 338 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 project-nano 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nano FrontEnd 2 | 3 | [[版本历史/ChangeLog](CHANGELOG.md)] 4 | 5 | [English Version](#introduce) 6 | 7 | ### 简介 8 | 9 | FrontEnd是Nano项目自带的Web管理门户,对集群进行图形化管理。FrontEnd的服务基于[Core模块](https://github.com/project-nano/core)的API接口实现,独立进行账号管理。 10 | 11 | 由于涉及网络配置,建议使用专用Installer进行部署,项目最新版本请访问[此地址](https://github.com/project-nano/releases) 12 | 13 | [项目官网](https://nanos.cloud/) 14 | 15 | [项目全部源代码](https://github.com/project-nano) 16 | 17 | [Portal/页面源文件](https://github.com/project-nano/portal) 18 | 19 | ### 编译 20 | 21 | 环境要求 22 | 23 | - CentOS 7 x86 24 | - Golang 1.20 25 | 26 | ``` 27 | 准备依赖的framework 28 | $git clone https://github.com/project-nano/framework.git 29 | 30 | 准备编译源代码 31 | $git clone https://github.com/project-nano/frontend.git 32 | 33 | 编译 34 | $cd frontend 35 | $go build 36 | ``` 37 | 38 | 编译成功在当前目录生成二进制文件frontend 39 | 40 | ### 使用 41 | 42 | 环境要求 43 | 44 | - CentOS 7 x86 45 | 46 | 47 | 48 | ``` 49 | 执行以下指令,启动FrontEnd模块 50 | $./frontend start 51 | 52 | 也可以使用绝对地址调用或者写入开机启动脚本,比如 53 | $/opt/nano/frontend/frontend start 54 | 55 | ``` 56 | 57 | 模块启动后会输出提供形如http://192.168.5.3:5870的Web访问地址,在浏览器中打开即可。 58 | 59 | 模块运行日志输入在log/frontend.log文件中,用于查错和调试 60 | 61 | 62 | 63 | 页面初次启动,会提示创建超级用户账号,初始化之后就能开始对Nano集群进行管理了。 64 | 65 | !!! **请注意:FrontEnd没有密码重置功能或者特殊后门,请牢记管理员密码,遗失后将无法访问系统**!!! 66 | 67 | 此外,除了模块启动功能,FrontEnd还支持以下命令参数启动 68 | 69 | | 命令名 | 说明 | 70 | | ------ | ---------------------------------- | 71 | | start | 启动服务 | 72 | | stop | 停止服务 | 73 | | status | 检查当前服务状态 | 74 | | halt | 强行中止服务(用于服务异常时重启) | 75 | 76 | 77 | 78 | ### 配置 79 | 80 | 模块关键配置信息存储在'config/frontend.cfg' 81 | 82 | | 参数 | 值类型 | 默认值 | 说明 | 83 | | ---------------- | ------ | ------------------------------------------- | ------------------------------------------------------------ | 84 | | **address** | 字符串 | | 提供管理页面服务的主机地址,IPv4格式 | 85 | | **port** | 整数 | 5870 | 提供管理页面服务的主机端口,默认5870 | 86 | | **service_host** | 字符串 | | Core模块API服务的主机地址,需要与Core模块配置一致 | 87 | | **service_port** | 整数 | 5850 | Core模块API服务的监听端口,需要与Core模块配置一致 | 88 | | **api_key** | 字符串 | ‘ThisIsAKeyPlaceHolder_ChangeToYourContent’ | 用于Core模块API服务校验的密文,需要与Core模块配置一致 | 89 | | **api_id** | 字符串 | ‘dummyID’ | 用于Core模块API服务校验的标识ID,需要与Core模块配置一致 | 90 | | **web_root** | 字符串 | ‘web_root’ | [Portal项目](https://github.com/project-nano/portal)生成的页面文件存放路径 | 91 | 92 | 假设FrontEnd模块地址为192.168.1.167,Core模块地址192.168.1.168,示例配置文件如下 93 | 94 | ```json 95 | { 96 | "address": "192.168.1.167", 97 | "port": 5870, 98 | "service_host": "192.168.1.168", 99 | "service_port": 5850, 100 | "api_key": "ThisIsAKeyPlaceHolder_ChangeToYourContent", 101 | "api_id": "dummyID", 102 | "web_root": "web_root" 103 | } 104 | ``` 105 | 106 | 107 | 108 | ### 目录结构 109 | 110 | 模块主要目录和文件如下 111 | 112 | | 目录/文件 | 说明 | 113 | | ------------------- | ------------------------ | 114 | | frontend | 模块二进制执行文件 | 115 | | config/frontend.cfg | 模块配置文件 | 116 | | data/log | 管理页面操作日志存储文件 | 117 | | log/frontend.log | 模块运行日志 | 118 | | web_root | 页面文件存放目录 | 119 | | web_root/index.html | 页面入口 | 120 | 121 | 122 | 123 | ### Introduce 124 | 125 | FrontEnd is the web portal that comes with the Nano project, managing clusters via GUI. The service is implemented based on the API interfaces of the [Core module](https://github.com/project-nano/core), with its own accounts system. 126 | 127 | It is recommended to use a dedicated Installer for deployment. For the latest project version, please visit [this address](https://github.com/project-nano/releases). 128 | 129 | [Official Project Website](https://us.nanos.cloud/en/) 130 | 131 | [Full Source Code of the Project](https://github.com/project-nano) 132 | 133 | [Portal page source code](https://github.com/project-nano/portal) 134 | 135 | ### Compilation 136 | 137 | Requirements 138 | 139 | - CentOS 7 x86 140 | - Golang 1.20 141 | 142 | ``` 143 | Prepare the dependent framework 144 | $git clone https://github.com/project-nano/framework.git 145 | 146 | Prepare the source code for compilation 147 | $git clone https://github.com/project-nano/frontend.git 148 | 149 | Compile 150 | $cd frontend 151 | $go build 152 | ``` 153 | 154 | The compiled binary file "frontend" will be generated in the current directory when success. 155 | 156 | ### Usage 157 | 158 | Requirements 159 | 160 | - CentOS 7 x86 161 | 162 | ``` 163 | Start module 164 | $./frontend start 165 | 166 | Alternatively, you can use an absolute address or write it into a startup script, such as: 167 | $/opt/nano/frontend/frontend start 168 | ``` 169 | 170 | After the module starts, it will provide a web address that can be accessed via browser, such as http://192.168.5.3:5870. 171 | 172 | The module logs are output to file: log/frontend.log 173 | 174 | **!!! Please note: There is no password recover or backdoor in FrontEnd. Please remember your password carefully, or it will lost forever. !!!** 175 | 176 | FrontEnd also supports the following commands: 177 | 178 | | Command | Description | 179 | | ------- | ------------------------------------------------------------ | 180 | | start | Start the service | 181 | | stop | Stop the service | 182 | | status | Check the current service status | 183 | | halt | Forcefully terminate the service (restart when there is an exception) | 184 | 185 | ### Configuration 186 | 187 | The main configuration is stored in file: config/frontend.cfg 188 | 189 | | Parameter | Value Type | Default Value | Description | 190 | | ---------------- | ---------- | ------------------------------------------- | ------------------------------------------------------------ | 191 | | **address** | String | | The host address for the management portal, IPv4 such as '192.168.3.1' | 192 | | **port** | Integer | 5870 | The host port for the management portal, default is 5870 | 193 | | **service_host** | String | | The host address of API service of Core module. Must be the same as the configuration of Core module | 194 | | **service_port** | Integer | 5850 | The listening port of API service of Core module. Must be the same as the configuration of Core module. default is 5850 | 195 | | **api_key** | String | 'ThisIsAKeyPlaceHolder_ChangeToYourContent' | The encryption text used for verifying API service, must be the same as the configuration of Core module. | 196 | | **api_id** | String | 'dummyID' | The ID used for verifying API service, Must be the same as the configuration of Core module. | 197 | | **web_root** | String | 'web_root' | [Portal](https://github.com/project-nano/portal) generated page files | 198 | 199 | Assuming that the FrontEnd module address is 192.168.1.167 and the Core module address is 192.168.1.168, an example configuration file is as follows: 200 | 201 | ```json 202 | { 203 | "address": "192.168.1.167", 204 | "port": 5870, 205 | "service_host": "192.168.1.168", 206 | "service_port": 5850, 207 | "api_key": "ThisIsAKeyPlaceHolder_ChangeToYourContent", 208 | "api_id": "dummyID", 209 | "web_root": "web_root" 210 | } 211 | ``` 212 | 213 | ### Directory Structure 214 | 215 | | Directory/File | Description | 216 | | ------------------- | ----------------------------------- | 217 | | frontend | Binary execution file of the module | 218 | | config/frontend.cfg | Configuration file | 219 | | data/log | Web operation logs | 220 | | log/frontend.log | Module running log | 221 | | web_root | Web page files | 222 | | web_root/index.html | Web entry point | 223 | 224 | -------------------------------------------------------------------------------- /channel_handler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/gorilla/websocket" 7 | "github.com/julienschmidt/httprouter" 8 | "github.com/pkg/errors" 9 | "log" 10 | "net" 11 | "net/http" 12 | "time" 13 | ) 14 | 15 | func (service *FrontEndService)handleCreateChannel(w http.ResponseWriter, r *http.Request, params httprouter.Params){ 16 | if _, err := service.getLoggedSession(w, r); err != nil{ 17 | ResponseFail(DefaultServerError, err.Error(), w) 18 | return 19 | } 20 | type userRequest struct { 21 | Guest string `json:"guest"` 22 | } 23 | var request userRequest 24 | var err error 25 | var decoder = json.NewDecoder(r.Body) 26 | if err = decoder.Decode(&request);err != nil{ 27 | log.Printf("<%s> [create channel] parse request fail: %s", r.RemoteAddr, err.Error()) 28 | ResponseFail(DefaultServerError, err.Error(), w) 29 | return 30 | } 31 | { 32 | log.Printf("<%s> [create channel] recv request for guest '%s'", r.RemoteAddr, request.Guest) 33 | type NetworkConfig struct { 34 | DisplayAddress string `json:"display_address"` 35 | } 36 | type MonitorConfig struct { 37 | MonitorSecret string `json:"monitor_secret"` 38 | Internal NetworkConfig `json:"internal"` 39 | } 40 | type userResponse struct { 41 | ErrorCode int `json:"error_code"` 42 | Message string `json:"message"` 43 | Data MonitorConfig `json:"data"` 44 | } 45 | var getGuestRequest *http.Request 46 | if getGuestRequest, err = http.NewRequest("GET", fmt.Sprintf("%s/guests/%s", service.backendURL, request.Guest), nil); err != nil{ 47 | log.Printf("<%s> [create channel] build request fail: %s", r.RemoteAddr, err.Error()) 48 | ResponseFail(DefaultServerError, err.Error(), w) 49 | return 50 | } 51 | getGuestRequest.Host = service.backendHost 52 | for name, value := range r.Header{ 53 | getGuestRequest.Header.Set(name, value[0]) 54 | } 55 | if err = service.generateRequestSignature(getGuestRequest); err != nil{ 56 | log.Printf("<%s> [create channel] signature request fail: %s", r.RemoteAddr, err.Error()) 57 | ResponseFail(DefaultServerError, err.Error(), w) 58 | return 59 | } 60 | var client = &http.Client{Timeout: time.Second * 10} 61 | 62 | resp, err := client.Do(getGuestRequest) 63 | if err != nil{ 64 | log.Printf("<%s> [create channel] get guest config fail: %s", r.RemoteAddr, err.Error()) 65 | ResponseFail(DefaultServerError, err.Error(), w) 66 | return 67 | } 68 | defer resp.Body.Close() 69 | var result userResponse 70 | decoder = json.NewDecoder(resp.Body) 71 | if err = decoder.Decode(&result);err != nil{ 72 | log.Printf("<%s> [create channel] parse guest config fail: %s", r.RemoteAddr, err.Error()) 73 | ResponseFail(DefaultServerError, err.Error(), w) 74 | return 75 | } 76 | if 0 != result.ErrorCode{ 77 | log.Printf("<%s> [create channel] get guest config fail: %s", r.RemoteAddr, result.Message) 78 | ResponseFail(DefaultServerError, result.Message, w) 79 | return 80 | } 81 | var respChan = make(chan ChannelResult) 82 | service.channelManager.CreateChannel(result.Data.Internal.DisplayAddress, result.Data.MonitorSecret, respChan) 83 | var tokenResult = <- respChan 84 | if tokenResult.Error != nil{ 85 | log.Printf("<%s> [create channel] create channel fail: %s", r.RemoteAddr, tokenResult.Error.Error()) 86 | ResponseFail(DefaultServerError, tokenResult.Error.Error(), w) 87 | return 88 | } 89 | log.Printf("<%s> [create channel] channel '%s' created", r.RemoteAddr, tokenResult.ID) 90 | ResponseOK(tokenResult.MonitorToken, w) 91 | } 92 | } 93 | 94 | func (service *FrontEndService)handleEstablishChannel(w http.ResponseWriter, r *http.Request, params httprouter.Params){ 95 | var channelID = params.ByName("id") 96 | if "" == channelID{ 97 | err := errors.New("must specify channel id") 98 | log.Printf("<%s> [establish channel] parse request fail: %s", r.RemoteAddr, err.Error()) 99 | ResponseFail(DefaultServerError, err.Error(), w) 100 | return 101 | } 102 | log.Printf("<%s> [establish channel] channel '%s'", r.RemoteAddr, channelID) 103 | var respChan = make(chan ChannelResult) 104 | service.channelManager.EstablishChannel(channelID, respChan) 105 | var result = <- respChan 106 | if result.Error != nil{ 107 | log.Printf("<%s> [establish channel] get channel fail: %s", r.RemoteAddr, result.Error.Error()) 108 | ResponseFail(DefaultServerError, result.Error.Error(), w) 109 | return 110 | } 111 | var targetAddress = result.Address 112 | const ( 113 | DefaultSubProtocol = "binary" 114 | VncProtocol = "tcp" 115 | ) 116 | var upgrader = websocket.Upgrader{ 117 | CheckOrigin: func(r *http.Request) bool { 118 | return true 119 | }, 120 | Subprotocols: []string{DefaultSubProtocol}, 121 | } 122 | wsConn, err := upgrader.Upgrade(w, r, nil) 123 | if err != nil{ 124 | log.Printf("<%s> [establish channel] upgrade fail: %s", r.RemoteAddr, err.Error()) 125 | ResponseFail(DefaultServerError, err.Error(), w) 126 | return 127 | } 128 | defer wsConn.Close() 129 | vncConn, err := net.Dial(VncProtocol, targetAddress) 130 | if err != nil{ 131 | log.Printf("<%s> [establish channel] open vnc channel fail: %s", r.RemoteAddr, err.Error()) 132 | ResponseFail(DefaultServerError, err.Error(), w) 133 | return 134 | } 135 | defer vncConn.Close() 136 | go forwardWebSocketToVnc(wsConn, vncConn, r.RemoteAddr) 137 | var buffer = make([]byte, 4 << 10) 138 | var n int 139 | for{ 140 | n, err = vncConn.Read(buffer) 141 | if err != nil{ 142 | log.Printf("<%s> [establish channel] recv from vnc fail: %s", r.RemoteAddr, err.Error()) 143 | break 144 | } 145 | if err = wsConn.WriteMessage(websocket.BinaryMessage, buffer[:n]); err != nil{ 146 | log.Printf("<%s> [establish channel] send to websocket fail: %s", r.RemoteAddr, err.Error()) 147 | break 148 | } 149 | } 150 | log.Printf("<%s> [establish channel] closed", r.RemoteAddr) 151 | } 152 | 153 | func forwardWebSocketToVnc(wsConn *websocket.Conn, vncConn net.Conn, address string) { 154 | for{ 155 | msgType, data, err := wsConn.ReadMessage() 156 | if err != nil{ 157 | log.Printf("<%s> [establish channel] recv from websocket fail: %s", address, err.Error()) 158 | break 159 | } 160 | if msgType != websocket.BinaryMessage{ 161 | log.Printf("<%s> [establish channel] ignore message type %d from websocket", address, msgType) 162 | continue 163 | } 164 | if _, err = vncConn.Write(data);err != nil{ 165 | log.Printf("<%s> [establish channel] write to vnc fail: %s", address, err.Error()) 166 | break 167 | } 168 | } 169 | log.Printf("<%s> [establish channel] forward finished", address) 170 | } -------------------------------------------------------------------------------- /channel_manager.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/project-nano/framework" 5 | "log" 6 | "time" 7 | "github.com/satori/go.uuid" 8 | "fmt" 9 | ) 10 | 11 | type MonitorToken struct { 12 | ID string `json:"id"` 13 | Protocol string `json:"protocol"` 14 | Username string `json:"username"` 15 | Password string `json:"password"` 16 | } 17 | 18 | type ChannelResult struct { 19 | Error error 20 | Address string 21 | MonitorToken 22 | } 23 | 24 | type channelCommand struct { 25 | Type int 26 | Address string 27 | Secret string 28 | Channel string 29 | Result chan ChannelResult 30 | } 31 | 32 | const ( 33 | ChannelCommandCreate = iota 34 | ChannelCommandEstablish 35 | ) 36 | 37 | type MonitorChannel struct { 38 | Protocol string 39 | Address string 40 | Username string 41 | Password string 42 | Expire time.Time 43 | } 44 | 45 | type ChannelManager struct { 46 | channels map[string]MonitorChannel 47 | commands chan channelCommand 48 | runner *framework.SimpleRunner 49 | } 50 | 51 | func CreateChannelManager() (manager *ChannelManager, err error){ 52 | const ( 53 | DefaultQueueSize = 1 << 10 54 | ) 55 | manager = &ChannelManager{} 56 | manager.channels = map[string]MonitorChannel{} 57 | manager.commands = make(chan channelCommand, DefaultQueueSize) 58 | manager.runner = framework.CreateSimpleRunner(manager.Routine) 59 | return 60 | } 61 | 62 | 63 | func (manager *ChannelManager)CreateChannel(address, secret string, respChan chan ChannelResult){ 64 | manager.commands <- channelCommand{Type:ChannelCommandCreate, Address:address, Secret:secret, Result:respChan} 65 | } 66 | 67 | func (manager *ChannelManager)EstablishChannel(channel string, respChan chan ChannelResult){ 68 | manager.commands <- channelCommand{Type:ChannelCommandEstablish, Channel:channel, Result:respChan} 69 | } 70 | 71 | func (manager *ChannelManager) Start() error{ 72 | return manager.runner.Start() 73 | } 74 | 75 | func (manager *ChannelManager) Stop() error{ 76 | return manager.runner.Stop() 77 | } 78 | 79 | func (manager *ChannelManager)Routine(c framework.RoutineController){ 80 | log.Println(" started") 81 | const ( 82 | CheckInterval = 2*time.Second 83 | ) 84 | var checkTicker = time.NewTicker(CheckInterval) 85 | 86 | for !c.IsStopping(){ 87 | select { 88 | case <- c.GetNotifyChannel(): 89 | log.Println(" stopping...") 90 | c.SetStopping() 91 | case <- checkTicker.C: 92 | manager.checkChannels() 93 | case cmd := <- manager.commands: 94 | manager.handleCommand(cmd) 95 | } 96 | } 97 | c.NotifyExit() 98 | log.Println(" stopped") 99 | } 100 | 101 | func (manager *ChannelManager)checkChannels(){ 102 | if 0 == len(manager.channels){ 103 | return 104 | } 105 | var now = time.Now() 106 | var clearList []string 107 | for id, channel := range manager.channels{ 108 | if now.After(channel.Expire){ 109 | clearList = append(clearList, id) 110 | } 111 | } 112 | if 0 != len(clearList){ 113 | for _, id := range clearList{ 114 | log.Printf(" channel %s expired", id) 115 | delete(manager.channels, id) 116 | } 117 | } 118 | } 119 | 120 | func (manager *ChannelManager)handleCommand(cmd channelCommand) { 121 | var err error 122 | switch cmd.Type { 123 | case ChannelCommandCreate: 124 | err = manager.handleCreateChannel(cmd.Address, cmd.Secret, cmd.Result) 125 | case ChannelCommandEstablish: 126 | err = manager.handleEstablishChannel(cmd.Channel, cmd.Result) 127 | default: 128 | log.Printf(" invalid command type %d", cmd.Type) 129 | } 130 | if err != nil{ 131 | log.Printf(" handle command type %d fail: %s", cmd.Type, err.Error()) 132 | } 133 | } 134 | 135 | func (manager *ChannelManager)handleCreateChannel(address, secret string, respChan chan ChannelResult) (err error){ 136 | const ( 137 | ChannelTimeout = 15*time.Second 138 | DefaultProtocol = "vnc" 139 | ) 140 | var newID = uuid.NewV4() 141 | 142 | var channelID = newID.String() 143 | if _, exists := manager.channels[channelID];exists{ 144 | err = fmt.Errorf("channel '%s' already exists", channelID) 145 | respChan <- ChannelResult{Error:err} 146 | return err 147 | } 148 | var expire = time.Now().Add(ChannelTimeout) 149 | var channel = MonitorChannel{DefaultProtocol, address, "", secret, expire} 150 | manager.channels[channelID] = channel 151 | log.Printf(" new channel '%s' created, address '%s', secret '%s'", channelID, address, secret) 152 | var token = MonitorToken{channelID, channel.Protocol, channel.Username, channel.Password} 153 | respChan <- ChannelResult{MonitorToken:token} 154 | return nil 155 | } 156 | 157 | func (manager *ChannelManager)handleEstablishChannel(channelID string, respChan chan ChannelResult) error{ 158 | channel, exists := manager.channels[channelID] 159 | if !exists{ 160 | var err = fmt.Errorf("invalid channel '%s'", channelID) 161 | log.Printf(" establish channel fail: %s", err.Error()) 162 | respChan <- ChannelResult{Error:err} 163 | return err 164 | } 165 | respChan <- ChannelResult{Address:channel.Address} 166 | log.Printf(" channel '%s' established", channelID) 167 | delete(manager.channels, channelID) 168 | return nil 169 | } 170 | -------------------------------------------------------------------------------- /daemon.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "github.com/project-nano/framework" 8 | "os" 9 | "path/filepath" 10 | ) 11 | 12 | type FrontEndConfig struct { 13 | ListenAddress string `json:"address"` 14 | ListenPort int `json:"port"` 15 | ServiceHost string `json:"service_host"` 16 | ServicePort int `json:"service_port"` 17 | APIKey string `json:"api_key"` 18 | APIID string `json:"api_id"` 19 | WebRoot string `json:"web_root"` 20 | CORSEnable bool `json:"cors_enable,omitempty"` 21 | MaxCores int `json:"max_cores,omitempty"` 22 | MaxMemory int `json:"max_memory,omitempty"` 23 | MaxDisk int `json:"max_disk,omitempty"` 24 | } 25 | 26 | type MainService struct { 27 | frontend *FrontEndService 28 | } 29 | 30 | const ( 31 | ExecuteName = "frontend" 32 | ConfigFileName = "frontend.cfg" 33 | ConfigPathName = "config" 34 | WebRootName = "web_root" 35 | DataPathName = "data" 36 | DefaultMaxCores = 24 37 | DefaultMaxMemory = 32 38 | DefaultMaxDisk = 64 39 | ) 40 | 41 | func (service *MainService) Start() (output string, err error) { 42 | if nil == service.frontend { 43 | err = errors.New("invalid service") 44 | return 45 | } 46 | if err = service.frontend.Start(); err != nil { 47 | return 48 | } 49 | output = fmt.Sprintf("Front-End Module %s\nCore API: %s\nNano Web Portal: http://%s\n", 50 | service.frontend.GetVersion(), 51 | service.frontend.GetBackendURL(), 52 | service.frontend.GetListenAddress()) 53 | return 54 | } 55 | 56 | func (service *MainService) Stop() (output string, err error) { 57 | if nil == service.frontend { 58 | err = errors.New("invalid service") 59 | return 60 | } 61 | err = service.frontend.Stop() 62 | return 63 | } 64 | 65 | func (service *MainService) Snapshot() (output string, err error) { 66 | output = "hello, this is stub for snapshot" 67 | return 68 | } 69 | 70 | func generateConfigure(workingPath string) (err error) { 71 | const ( 72 | DefaultPathPerm = 0740 73 | ) 74 | var configPath = filepath.Join(workingPath, ConfigPathName) 75 | if _, err = os.Stat(configPath); os.IsNotExist(err) { 76 | //create path 77 | err = os.Mkdir(configPath, DefaultPathPerm) 78 | if err != nil { 79 | return 80 | } 81 | fmt.Printf("config path %s created\n", configPath) 82 | } 83 | 84 | var configFile = filepath.Join(configPath, ConfigFileName) 85 | if _, err = os.Stat(configFile); os.IsNotExist(err) { 86 | fmt.Println("No configures available, following instructions to generate a new one.") 87 | const ( 88 | DefaultConfigPerm = 0640 89 | DefaultBackEndPort = 5850 90 | DefaultFrontEndPort = 5870 91 | ) 92 | var defaultWebRoot = filepath.Join(workingPath, WebRootName) 93 | var config = FrontEndConfig{ 94 | MaxCores: DefaultMaxCores, 95 | MaxMemory: DefaultMaxMemory, 96 | MaxDisk: DefaultMaxDisk, 97 | } 98 | if config.ListenAddress, err = framework.ChooseIPV4Address("Portal listen address"); err != nil { 99 | return 100 | } 101 | if config.ListenPort, err = framework.InputInteger("Portal listen port", DefaultFrontEndPort); err != nil { 102 | return 103 | } 104 | if config.ServiceHost, err = framework.InputString("Backend service address", config.ListenAddress); err != nil { 105 | return 106 | } 107 | if config.ServicePort, err = framework.InputInteger("Backend service port", DefaultBackEndPort); err != nil { 108 | return 109 | } 110 | if config.WebRoot, err = framework.InputString("Web Root Path", defaultWebRoot); err != nil { 111 | return 112 | } 113 | //write 114 | var data []byte 115 | data, err = json.MarshalIndent(config, "", " ") 116 | if err != nil { 117 | return err 118 | } 119 | if err = os.WriteFile(configFile, data, DefaultConfigPerm); err != nil { 120 | return err 121 | } 122 | fmt.Printf("default configure '%s' generated\n", configFile) 123 | } 124 | 125 | var dataPath = filepath.Join(workingPath, DataPathName) 126 | if _, err = os.Stat(dataPath); os.IsNotExist(err) { 127 | //create path 128 | err = os.Mkdir(dataPath, DefaultPathPerm) 129 | if err != nil { 130 | return 131 | } 132 | fmt.Printf("data path %s created\n", dataPath) 133 | } 134 | return 135 | } 136 | 137 | func createDaemon(workingPath string) (service framework.DaemonizedService, err error) { 138 | var configPath = filepath.Join(workingPath, ConfigPathName) 139 | var dataPath = filepath.Join(workingPath, DataPathName) 140 | if _, err = os.Stat(configPath); os.IsNotExist(err) { 141 | err = fmt.Errorf("config path %s not available", configPath) 142 | return nil, err 143 | } 144 | if _, err = os.Stat(dataPath); os.IsNotExist(err) { 145 | err = fmt.Errorf("data path %s not available", dataPath) 146 | return nil, err 147 | } 148 | var s = MainService{} 149 | s.frontend, err = CreateFrontEnd(configPath, dataPath) 150 | return &s, err 151 | } 152 | 153 | func main() { 154 | framework.ProcessDaemon(ExecuteName, generateConfigure, createDaemon) 155 | } 156 | -------------------------------------------------------------------------------- /fetch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | WebRoot=./web_root 3 | SourcePath=../portal 4 | WebSource=$SourcePath/build 5 | 6 | if [ ! -d "$WebSource" ]; then 7 | echo $WebSource not exists 8 | exit 1 9 | fi 10 | 11 | if [ -d "$WebRoot" ]; then 12 | #clear previous content 13 | if rm -Rf "$WebRoot/*" ; then 14 | echo previous web root content cleared 15 | else 16 | echo clear previous web root content fail 17 | exit 1 18 | fi 19 | elif mkdir "$WebRoot"; then 20 | echo new path "$WebRoot" created 21 | fi 22 | 23 | if cp -R $WebSource/* $WebRoot; then 24 | echo all web files in $WebSource copied to $WebRoot 25 | echo fetch success 26 | exit 0 27 | else 28 | echo fetch $WebSource fail 29 | exit 1 30 | fi 31 | 32 | 33 | -------------------------------------------------------------------------------- /front_service_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | "path/filepath" 7 | "testing" 8 | ) 9 | 10 | const ( 11 | testPath = "test" 12 | ) 13 | 14 | func clearTestEnvironment() (err error) { 15 | if _, err = os.Stat(testPath); !os.IsNotExist(err) { 16 | //exists 17 | err = os.RemoveAll(testPath) 18 | return 19 | } 20 | return nil 21 | } 22 | 23 | func getFrontEndServiceForTest() (service *FrontEndService, err error) { 24 | const ( 25 | DefaultPathPerm = 0740 26 | DefaultFilePerm = 0640 27 | webHost = "192.168.1.167" 28 | webPort = 5870 29 | serviceHost = webHost 30 | servicePort = 5850 31 | apiKey = "123456" 32 | apiID = "123456" 33 | webRoot = "web_root" 34 | ) 35 | if err = clearTestEnvironment(); err != nil { 36 | return 37 | } 38 | var configPath = filepath.Join(testPath, ConfigPathName) 39 | if err = os.MkdirAll(configPath, DefaultPathPerm); err != nil { 40 | return 41 | } 42 | //default configure 43 | var config = FrontEndConfig{ 44 | MaxCores: DefaultMaxCores, 45 | MaxMemory: DefaultMaxMemory, 46 | MaxDisk: DefaultMaxDisk, 47 | ListenAddress: webHost, 48 | ListenPort: webPort, 49 | ServiceHost: serviceHost, 50 | ServicePort: servicePort, 51 | WebRoot: webRoot, 52 | APIKey: apiKey, 53 | APIID: apiID, 54 | } 55 | var configFilename = filepath.Join(configPath, ConfigFileName) 56 | var configFile *os.File 57 | //create config 58 | if configFile, err = os.Create(configFilename); err != nil { 59 | return 60 | } 61 | defer func() { 62 | _ = configFile.Close() 63 | }() 64 | var encoder = json.NewEncoder(configFile) 65 | encoder.SetIndent("", " ") 66 | if err = encoder.Encode(config); err != nil { 67 | return 68 | } 69 | var dataPath = filepath.Join(testPath, DataPathName) 70 | //create service 71 | service, err = CreateFrontEnd(configPath, dataPath) 72 | return 73 | } 74 | 75 | func TestFrontEndService_StartAndStop(t *testing.T) { 76 | service, err := getFrontEndServiceForTest() 77 | if err != nil { 78 | t.Fatalf("load service fail: %s", err.Error()) 79 | return 80 | } 81 | if err = service.Start(); err != nil { 82 | t.Fatalf("start service fail: %s", err.Error()) 83 | return 84 | } 85 | if err = service.Stop(); err != nil { 86 | t.Fatalf("stop service fail: %s", err.Error()) 87 | } 88 | t.Log("test frontend service start and stop success") 89 | } 90 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/project-nano/frontend 2 | 3 | go 1.20 4 | 5 | //replace github.com/project-nano/framework => ../framework 6 | 7 | require ( 8 | github.com/gorilla/websocket v1.5.0 9 | github.com/julienschmidt/httprouter v1.3.0 10 | github.com/pkg/errors v0.9.1 11 | github.com/project-nano/framework v1.0.9 12 | github.com/satori/go.uuid v1.2.0 13 | golang.org/x/crypto v0.13.0 14 | ) 15 | 16 | require ( 17 | github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect 18 | github.com/klauspost/cpuid/v2 v2.2.5 // indirect 19 | github.com/klauspost/reedsolomon v1.11.8 // indirect 20 | github.com/project-nano/sonar v0.0.0-20190628085230-df7942628d6f // indirect 21 | github.com/sevlyar/go-daemon v0.1.6 // indirect 22 | github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect 23 | github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect 24 | github.com/tjfoc/gmsm v1.4.1 // indirect 25 | github.com/xtaci/kcp-go v5.4.17+incompatible // indirect 26 | github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 // indirect 27 | golang.org/x/net v0.15.0 // indirect 28 | golang.org/x/sys v0.12.0 // indirect 29 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 30 | ) 31 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 4 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 5 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 6 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 7 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 8 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 9 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 10 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 11 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 12 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 13 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 14 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 15 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 16 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 17 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 18 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 19 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 20 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 21 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 22 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 23 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 24 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 25 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 26 | github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= 27 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 28 | github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= 29 | github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= 30 | github.com/klauspost/cpuid/v2 v2.0.14/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= 31 | github.com/klauspost/cpuid/v2 v2.1.0/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= 32 | github.com/klauspost/cpuid/v2 v2.1.1 h1:t0wUqjowdm8ezddV5k0tLWVklVuvLJpoHeb4WBdydm0= 33 | github.com/klauspost/cpuid/v2 v2.1.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= 34 | github.com/klauspost/cpuid/v2 v2.1.2 h1:XhdX4fqAJUA0yj+kUwMavO0hHrSPAecYdYf1ZmxHvak= 35 | github.com/klauspost/cpuid/v2 v2.1.2/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= 36 | github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= 37 | github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 38 | github.com/klauspost/reedsolomon v1.10.0/go.mod h1:qHMIzMkuZUWqIh8mS/GruPdo3u0qwX2jk/LH440ON7Y= 39 | github.com/klauspost/reedsolomon v1.11.0 h1:fc24kMFf4I6dXJwSkVAsw8Za/dMcJrV5ImeDjG3ss1M= 40 | github.com/klauspost/reedsolomon v1.11.0/go.mod h1:FXLZzlJIdfqEnQLdUKWNRuMZg747hZ4oYp2Ml60Lb/k= 41 | github.com/klauspost/reedsolomon v1.11.8 h1:s8RpUW5TK4hjr+djiOpbZJB4ksx+TdYbRH7vHQpwPOY= 42 | github.com/klauspost/reedsolomon v1.11.8/go.mod h1:4bXRN+cVzMdml6ti7qLouuYi32KHJ5MGv0Qd8a47h6A= 43 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 44 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 45 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 46 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 47 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 48 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 49 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 50 | github.com/project-nano/framework v1.0.9 h1:n0WBKuEQu2o8i70vlmM75dxannWTPZ03ahfCLGAORLE= 51 | github.com/project-nano/framework v1.0.9/go.mod h1:wIJW6aMQqLGxwjGeXnlDn/+FJbDxIhiqMD4XArUNtUQ= 52 | github.com/project-nano/sonar v0.0.0-20190628085230-df7942628d6f h1:NorTDWkZl22V1v/2t0gG01ylSACOmjDmrGpN3R/Pbgg= 53 | github.com/project-nano/sonar v0.0.0-20190628085230-df7942628d6f/go.mod h1:VYPy/Adnn0NLwbDfa/7vv12vWbftUqTHZzwt83Q5QAo= 54 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 55 | github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= 56 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 57 | github.com/sevlyar/go-daemon v0.1.6 h1:EUh1MDjEM4BI109Jign0EaknA2izkOyi0LV3ro3QQGs= 58 | github.com/sevlyar/go-daemon v0.1.6/go.mod h1:6dJpPatBT9eUwM5VCw9Bt6CdX9Tk6UWvhW3MebLDRKE= 59 | github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU= 60 | github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU= 61 | github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b h1:fj5tQ8acgNUr6O8LEplsxDhUIe2573iLkJc+PqnzZTI= 62 | github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4= 63 | github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= 64 | github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= 65 | github.com/xtaci/kcp-go v4.3.4+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE= 66 | github.com/xtaci/kcp-go v5.4.17+incompatible h1:RudP76JCx062JSxPxSjBl+457+fS0M7T8zEZPLpC0o8= 67 | github.com/xtaci/kcp-go v5.4.17+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE= 68 | github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E= 69 | github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37/go.mod h1:HpMP7DB2CyokmAh4lp0EQnnWhmycP/TvwBGzvuie+H0= 70 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 71 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 72 | golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 73 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 74 | golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0 h1:a5Yg6ylndHHYJqIPrdq0AhvR6KTvDTAvgBtaidhEevY= 75 | golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 76 | golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= 77 | golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= 78 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 79 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 80 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 81 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 82 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 83 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 84 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 85 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 86 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 87 | golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 88 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 89 | golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 90 | golang.org/x/net v0.0.0-20220921203646-d300de134e69 h1:hUJpGDpnfwdJW8iNypFjmSY0sCBEL+spFTZ2eO+Sfps= 91 | golang.org/x/net v0.0.0-20220921203646-d300de134e69/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 92 | golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= 93 | golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= 94 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 95 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 96 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 97 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 98 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 99 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 100 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 101 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 102 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 103 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 104 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 105 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 106 | golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 107 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 108 | golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 h1:h+EGohizhe9XlX18rfpa8k8RAc5XyaeamM+0VHRd4lc= 109 | golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 110 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 111 | golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= 112 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 113 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 114 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 115 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 116 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 117 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 118 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 119 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 120 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 121 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 122 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 123 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 124 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 125 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 126 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 127 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 128 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 129 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 130 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 131 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 132 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 133 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 134 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 135 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 136 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 137 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 138 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 139 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 140 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 141 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 142 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 143 | -------------------------------------------------------------------------------- /log_agent.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | "fmt" 6 | "os" 7 | "bufio" 8 | "log" 9 | "path/filepath" 10 | "strconv" 11 | "strings" 12 | "io" 13 | ) 14 | 15 | type LogAgent struct { 16 | currentTime time.Time 17 | currentIndex int 18 | currentFile *os.File 19 | currentLogPath string 20 | fileWriter *bufio.Writer 21 | logRoot string 22 | logContent []string //from new to old 23 | contentModified bool 24 | } 25 | 26 | const ( 27 | EntryPrefixFormat = "20060102150405" 28 | Day = 24 * time.Hour 29 | MonthFormat = "200601" 30 | DateFormat = "20060102" 31 | ValidEntryIDLength = len(EntryPrefixFormat) + 3 32 | InitialEntryIndex = 1 33 | OpenFilePerm = 0640 34 | ) 35 | 36 | func CreateLogAgent(dataPath string) (agent *LogAgent, err error) { 37 | const ( 38 | logPathName = "log" 39 | DefaultPathPerm = 0740 40 | ) 41 | agent = &LogAgent{} 42 | agent.logContent = make([]string, 0) 43 | agent.contentModified = false 44 | agent.logRoot = filepath.Join(dataPath, logPathName) 45 | if _, err = os.Stat(agent.logRoot); os.IsNotExist(err){ 46 | if err = os.MkdirAll(agent.logRoot, DefaultPathPerm); err != nil{ 47 | return 48 | } 49 | log.Printf(" log root path '%s' created", agent.logRoot) 50 | } 51 | var today = time.Now().Truncate(Day) 52 | var monthPath = filepath.Join(agent.logRoot, today.Format(MonthFormat)) 53 | agent.currentLogPath = filepath.Join(monthPath, fmt.Sprintf("%s.log", today.Format(DateFormat))) 54 | if _, err = os.Stat(agent.currentLogPath); os.IsNotExist(err){ 55 | if _, err = os.Stat(monthPath); os.IsNotExist(err){ 56 | if err = os.Mkdir(monthPath, DefaultPathPerm); err != nil{ 57 | return 58 | } 59 | log.Printf(" new log path '%s' created", monthPath) 60 | } 61 | //today is a new day 62 | agent.currentTime = time.Now().Truncate(time.Second) 63 | agent.currentIndex = InitialEntryIndex 64 | err = agent.openCurrentLog() 65 | return 66 | } 67 | 68 | //load current 69 | var lastEntry LogEntry 70 | var entryAvailable = false 71 | 72 | { 73 | var scanSource *os.File 74 | scanSource, err = os.Open(agent.currentLogPath) 75 | if err != nil{ 76 | return 77 | } 78 | defer scanSource.Close() 79 | var scanner = bufio.NewScanner(scanSource) 80 | for scanner.Scan(){ 81 | var line = scanner.Text() 82 | agent.logContent = append(agent.logContent, line) 83 | if !entryAvailable{ 84 | lastEntry, err = parseLog(line) 85 | if err != nil{ 86 | return 87 | } 88 | entryAvailable = true 89 | } 90 | } 91 | 92 | } 93 | 94 | if entryAvailable{ 95 | agent.currentTime = lastEntry.Time.Truncate(time.Second) 96 | agent.currentIndex, err = strconv.Atoi(lastEntry.ID[len(EntryPrefixFormat):]) 97 | if err != nil{ 98 | log.Printf(" parse index from entry '%s' fail: %s", lastEntry.ID, err.Error()) 99 | return 100 | } 101 | log.Printf(" last entry '%s' (%d available) loaded from '%s'", lastEntry.ID, len(agent.logContent), agent.currentLogPath) 102 | 103 | }else{ 104 | agent.currentTime = time.Now().Truncate(time.Second) 105 | agent.currentIndex = InitialEntryIndex 106 | } 107 | err = agent.openCurrentLog() 108 | return 109 | } 110 | 111 | func (agent *LogAgent) Write(content string) (err error) { 112 | var now = time.Now() 113 | var entryTimeStamp = now.Truncate(time.Second) 114 | var entry LogEntry 115 | if entryTimeStamp.Equal(agent.currentTime) { 116 | entry.ID = fmt.Sprintf("%s%03d", entryTimeStamp.Format(EntryPrefixFormat), agent.currentIndex) 117 | agent.currentIndex++ 118 | }else{ 119 | if !now.Truncate(24*time.Hour).Equal(agent.currentTime.Truncate(24*time.Hour)){ 120 | //open new file for a new day 121 | if err = agent.Close(); err != nil{ 122 | return 123 | } 124 | agent.currentLogPath = filepath.Join(agent.logRoot, now.Format(MonthFormat), fmt.Sprintf("%s.log", now.Format(DateFormat))) 125 | if err = agent.openCurrentLog(); err != nil{ 126 | return 127 | } 128 | agent.logContent = make([]string, 0) 129 | agent.contentModified = false 130 | } 131 | //reset mark to second.1 132 | agent.currentTime = entryTimeStamp 133 | agent.currentIndex = InitialEntryIndex 134 | entry.ID = fmt.Sprintf("%s%03d", entryTimeStamp.Format(EntryPrefixFormat), agent.currentIndex) 135 | } 136 | entry.Time = now 137 | entry.Content = content 138 | //insert 139 | agent.logContent = append([]string{logToLine(entry)}, agent.logContent...) 140 | if !agent.contentModified{ 141 | agent.contentModified = true 142 | } 143 | return nil 144 | } 145 | 146 | func (agent *LogAgent) Remove(idList []string) (err error) { 147 | const ( 148 | IndexLength = 3 149 | MonthPrefixLength = 6 150 | ) 151 | var currentLocation = time.Now().Location() 152 | var targetMap = map[string]map[string]bool{} 153 | for _, entryID := range idList{ 154 | if len(entryID) != (len(EntryPrefixFormat) + IndexLength){ 155 | err = fmt.Errorf("invalid entry id '%s' with length %d", entryID, len(entryID)) 156 | return 157 | } 158 | timeStamp, err := time.ParseInLocation(EntryPrefixFormat, entryID[:len(EntryPrefixFormat)], currentLocation) 159 | if err != nil{ 160 | err = fmt.Errorf("invalid entry id prefix '%s'", entryID[:len(EntryPrefixFormat)]) 161 | return err 162 | } 163 | var dayString = timeStamp.Format(DateFormat) 164 | if _, exists := targetMap[dayString]; !exists{ 165 | targetMap[dayString] = map[string]bool{entryID: true} 166 | }else{ 167 | targetMap[dayString][entryID] = true 168 | } 169 | } 170 | for dayString, targets := range targetMap{ 171 | var month = dayString[:MonthPrefixLength] 172 | var logFilePath = filepath.Join(agent.logRoot, month, fmt.Sprintf("%s.log", dayString)) 173 | if _, err = os.Stat(logFilePath); os.IsNotExist(err){ 174 | err = fmt.Errorf("can not find log file '%s'", logFilePath) 175 | return 176 | } 177 | var isCurrentLog = logFilePath == agent.currentLogPath 178 | 179 | var logLines []string 180 | if isCurrentLog{ 181 | logLines = agent.logContent 182 | }else{ 183 | //load all entries 184 | var logFile *os.File 185 | logFile, err = os.Open(logFilePath) 186 | if err != nil{ 187 | err = fmt.Errorf("open log file fail: %s", err.Error()) 188 | return err 189 | } 190 | var logScanner = bufio.NewScanner(logFile) 191 | for logScanner.Scan() { 192 | logLines = append(logLines, logScanner.Text()) 193 | } 194 | logFile.Close() 195 | } 196 | var removeResult = make([]string, 0) 197 | for _, line := range logLines{ 198 | entry, err := parseLog(line) 199 | if err != nil{ 200 | return err 201 | } 202 | if _, exists := targets[entry.ID]; exists{ 203 | delete(targets, entry.ID) 204 | log.Printf(" entry '%s' removed from '%s'", entry.ID, logFilePath) 205 | }else{ 206 | removeResult = append(removeResult, line) 207 | } 208 | } 209 | if isCurrentLog{ 210 | agent.logContent = removeResult 211 | agent.contentModified = true 212 | }else{ 213 | //rewrite all lines 214 | var logFile *os.File 215 | logFile, err = os.OpenFile(logFilePath, os.O_WRONLY|os.O_APPEND|os.O_TRUNC, OpenFilePerm) 216 | if err != nil{ 217 | err = fmt.Errorf("rewrite log file fail: %s", err.Error()) 218 | return err 219 | } 220 | var writer = bufio.NewWriter(logFile) 221 | for _, line := range removeResult{ 222 | _, err = writer.WriteString(line) 223 | if err != nil{ 224 | return err 225 | } 226 | if err = writer.WriteByte('\n'); err != nil{ 227 | return err 228 | } 229 | } 230 | writer.Flush() 231 | logFile.Close() 232 | log.Printf(" %d lines rewrite to '%s'", len(removeResult), logFilePath) 233 | } 234 | } 235 | log.Printf(" %d entries removed", len(idList)) 236 | return nil 237 | } 238 | 239 | func (agent *LogAgent) Query(condition LogQueryCondition) (logs []LogEntry, total uint, err error) { 240 | const ( 241 | MaxEntry = 1000 242 | ) 243 | total = 0 244 | if condition.EndTime.Before(condition.BeginTime){ 245 | err = fmt.Errorf("invalid time range (%s ~ %s)", condition.BeginTime.Format(TimeFormatLayout), condition.EndTime.Format(TimeFormatLayout)) 246 | return 247 | } 248 | var queried, offset = 0, 0 249 | var inTimeRange = false 250 | for date := condition.EndTime; date.After(condition.BeginTime); date = date.Add(-Day){ 251 | var logFilePath = filepath.Join(agent.logRoot, date.Format(MonthFormat), fmt.Sprintf("%s.log", date.Format(DateFormat))) 252 | 253 | var isCurrentLog = logFilePath == agent.currentLogPath 254 | var logLines []string 255 | if isCurrentLog{ 256 | logLines = agent.logContent 257 | //log.Printf(" debug: %d lines loaded from current log", len(logLines)) 258 | }else{ 259 | if _, err = os.Stat(logFilePath); os.IsNotExist(err){ 260 | //log.Printf(" warning: query ignores absent log '%s'", logFilePath) 261 | continue 262 | } 263 | var logFile *os.File 264 | logFile, err = os.Open(logFilePath) 265 | if err != nil{ 266 | return 267 | } 268 | var scanner = bufio.NewScanner(logFile) 269 | for scanner.Scan(){ 270 | logLines = append(logLines, scanner.Text()) 271 | } 272 | logFile.Close() 273 | } 274 | 275 | 276 | for _, line := range logLines { 277 | var entry LogEntry 278 | entry, err = parseLog(line) 279 | if err != nil{ 280 | log.Printf(" parse line %d of log '%s' fail: %s", offset, logFilePath, err.Error()) 281 | return 282 | } 283 | if !inTimeRange { 284 | if entry.Time.Before(condition.EndTime){ 285 | //range start 286 | inTimeRange = true 287 | } 288 | } 289 | if inTimeRange{ 290 | if offset < condition.Start{ 291 | total++ 292 | offset++ 293 | continue 294 | } 295 | if entry.Time.Before(condition.BeginTime){ 296 | break 297 | } 298 | if total >= MaxEntry{ 299 | break 300 | } 301 | total++ 302 | if queried < condition.Limit{ 303 | //store log 304 | logs = append(logs, entry) 305 | queried++ 306 | } 307 | } 308 | }//end scanner 309 | if total >= MaxEntry{ 310 | break 311 | } 312 | } 313 | //log.Printf(" %d / %d entries queried between (%s ~ %s), start from %d, with limit %d", 314 | // len(logs), total, 315 | // condition.BeginTime.Format(TimeFormatLayout), 316 | // condition.EndTime.Format(TimeFormatLayout), 317 | // condition.Start, condition.Limit) 318 | return logs, total,nil 319 | } 320 | 321 | func (agent *LogAgent) Flush() (err error){ 322 | if !agent.contentModified{ 323 | return nil 324 | } 325 | if _, err = agent.currentFile.Seek(0, io.SeekStart); err != nil{ 326 | return 327 | } 328 | agent.fileWriter.Reset(agent.currentFile) 329 | for _, log := range agent.logContent{ 330 | if _, err = agent.fileWriter.WriteString(log); err != nil{ 331 | return 332 | } 333 | if _, err = agent.fileWriter.WriteString("\n"); err != nil{ 334 | return 335 | } 336 | } 337 | if err = agent.fileWriter.Flush(); err != nil{ 338 | return 339 | } 340 | agent.contentModified = false 341 | return nil 342 | } 343 | 344 | func (agent *LogAgent) Close() (err error){ 345 | if err = agent.Flush(); err != nil{ 346 | return 347 | } 348 | return agent.closeCurrentLog() 349 | } 350 | 351 | func (agent *LogAgent) openCurrentLog() (err error){ 352 | if _, err = os.Stat(agent.currentLogPath); os.IsNotExist(err){ 353 | agent.currentFile, err = os.Create(agent.currentLogPath) 354 | if err != nil{ 355 | return 356 | } 357 | log.Printf(" current log '%s' created", agent.currentLogPath) 358 | }else{ 359 | //open 360 | agent.currentFile, err = os.OpenFile(agent.currentLogPath, os.O_RDWR|os.O_CREATE, OpenFilePerm) 361 | if err != nil{ 362 | return 363 | } 364 | log.Printf(" current log '%s' opened", agent.currentLogPath) 365 | } 366 | agent.fileWriter = bufio.NewWriter(agent.currentFile) 367 | return nil 368 | } 369 | 370 | func (agent *LogAgent) closeCurrentLog() (err error){ 371 | if err = agent.fileWriter.Flush(); err != nil{ 372 | return 373 | } 374 | if err = agent.currentFile.Close(); err != nil{ 375 | return 376 | } 377 | agent.currentFile = nil 378 | log.Printf(" current log '%s' closed", agent.currentLogPath) 379 | return nil 380 | } 381 | 382 | func parseLog(line string) (entry LogEntry, err error) { 383 | const ( 384 | separator = "," 385 | ) 386 | var idTail = strings.Index(line, separator) 387 | if idTail <= 0 { 388 | err = fmt.Errorf("entry ID missed in line %s", line) 389 | return 390 | } 391 | if idTail != ValidEntryIDLength{ 392 | err = fmt.Errorf("invalid entry ID '%s'", line[:idTail]) 393 | return 394 | } 395 | entry.ID = line[:idTail] 396 | var timeTail = strings.Index(line[(idTail + 1):], separator) 397 | if timeTail <= 0{ 398 | err = fmt.Errorf("entry time missed in line %s", line) 399 | return 400 | } 401 | 402 | var timeInString = line[(idTail + 1): (idTail + 1 + timeTail)] 403 | var unixnano int64 404 | unixnano, err = strconv.ParseInt(timeInString, 10, 64) 405 | if err != nil{ 406 | err = fmt.Errorf("invalid time stamp '%s'", timeInString) 407 | return 408 | } 409 | entry.Time = time.Unix(0, unixnano) 410 | entry.Content = line[(idTail + timeTail + 2):] 411 | return entry, nil 412 | } 413 | 414 | func logToLine(entry LogEntry) (line string){ 415 | return fmt.Sprintf("%s,%d,%s", entry.ID, entry.Time.UnixNano(), entry.Content) 416 | } 417 | -------------------------------------------------------------------------------- /log_manager.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | "github.com/project-nano/framework" 6 | "log" 7 | ) 8 | 9 | type logCommandType int 10 | 11 | const ( 12 | cmdQueryLog = iota 13 | cmdAddLog 14 | cmdRemoveLog 15 | ) 16 | 17 | const ( 18 | TimeFormatLayout = "2006-01-02 15:04:05" 19 | ) 20 | 21 | type LogManager struct { 22 | agent *LogAgent 23 | commands chan logCommand 24 | runner *framework.SimpleRunner 25 | } 26 | 27 | type LogQueryCondition struct { 28 | Limit int 29 | Start int 30 | BeginTime time.Time 31 | EndTime time.Time 32 | } 33 | 34 | type LogEntry struct { 35 | ID string 36 | Time time.Time 37 | Content string 38 | } 39 | 40 | type LogResult struct { 41 | Error error 42 | Logs []LogEntry 43 | Total uint 44 | } 45 | 46 | type logCommand struct { 47 | Type logCommandType 48 | Condition LogQueryCondition 49 | Content string 50 | IDList []string 51 | ResultChan chan LogResult 52 | ErrorChan chan error 53 | } 54 | 55 | func CreateLogManager(dataPath string) (manager *LogManager, err error) { 56 | const ( 57 | DefaultQueueLength = 1 << 10 58 | ) 59 | manager = &LogManager{} 60 | manager.commands = make(chan logCommand, DefaultQueueLength) 61 | manager.runner = framework.CreateSimpleRunner(manager.Routine) 62 | manager.agent, err = CreateLogAgent(dataPath) 63 | return 64 | } 65 | 66 | func (manager *LogManager) Start() error{ 67 | return manager.runner.Start() 68 | } 69 | 70 | func (manager *LogManager) Stop() error{ 71 | return manager.runner.Stop() 72 | } 73 | 74 | func (manager *LogManager) Routine(c framework.RoutineController){ 75 | log.Println(" started") 76 | const ( 77 | FlushInterval = 30 * time.Second 78 | ) 79 | var flushTicker = time.NewTicker(FlushInterval) 80 | for !c.IsStopping() { 81 | select { 82 | case <-c.GetNotifyChannel(): 83 | c.SetStopping() 84 | case <- flushTicker.C: 85 | manager.agent.Flush() 86 | case cmd := <-manager.commands: 87 | manager.handleCommand(cmd) 88 | } 89 | } 90 | manager.agent.Close() 91 | c.NotifyExit() 92 | log.Println(" stopped") 93 | } 94 | 95 | func (manager *LogManager) QueryLog(condition LogQueryCondition, respChan chan LogResult) { 96 | manager.commands <- logCommand{Type:cmdQueryLog, Condition:condition, ResultChan:respChan} 97 | } 98 | 99 | func (manager *LogManager) AddLog(content string, respChan chan error) { 100 | manager.commands <- logCommand{Type:cmdAddLog, Content:content, ErrorChan:respChan} 101 | } 102 | 103 | func (manager *LogManager) RemoveLog(entries []string, respChan chan error) { 104 | manager.commands <- logCommand{Type:cmdRemoveLog, IDList:entries, ErrorChan:respChan} 105 | } 106 | 107 | func (manager *LogManager) handleCommand(cmd logCommand){ 108 | var err error 109 | switch cmd.Type { 110 | case cmdQueryLog: 111 | err = manager.handleQueryLog(cmd.Condition, cmd.ResultChan) 112 | case cmdAddLog: 113 | err = manager.handleAddLog(cmd.Content, cmd.ErrorChan) 114 | case cmdRemoveLog: 115 | err = manager.handleRemoveLog(cmd.IDList, cmd.ErrorChan) 116 | default: 117 | log.Printf(" unsupport command type %d", cmd.Type) 118 | } 119 | if err != nil{ 120 | log.Printf(" handle command %d fail: %s", cmd.Type, err.Error()) 121 | } 122 | } 123 | 124 | func (manager *LogManager) handleQueryLog(condition LogQueryCondition, respChan chan LogResult) (err error) { 125 | logs, total, err := manager.agent.Query(condition) 126 | if err != nil{ 127 | respChan <- LogResult{Error:err} 128 | return 129 | } 130 | respChan <- LogResult{Logs:logs, Total: total} 131 | return nil 132 | } 133 | 134 | func (manager *LogManager) handleAddLog(content string, respChan chan error) (err error) { 135 | err = manager.agent.Write(content) 136 | respChan <- err 137 | return 138 | } 139 | 140 | func (manager *LogManager) handleRemoveLog(entries []string, respChan chan error) (err error) { 141 | err = manager.agent.Remove(entries) 142 | respChan <- err 143 | return 144 | } 145 | -------------------------------------------------------------------------------- /session_manager.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/project-nano/framework" 5 | "log" 6 | "fmt" 7 | "time" 8 | "github.com/satori/go.uuid" 9 | ) 10 | 11 | type LoggedSession struct { 12 | ID string 13 | User string 14 | Group string 15 | Menu []string 16 | Nonce string 17 | Key string 18 | Expire time.Time 19 | Timeout int 20 | Address string 21 | } 22 | 23 | type sessionCommandType int 24 | 25 | const ( 26 | cmdAllocateSession = iota 27 | cmdUpdateSession 28 | cmdGetSession 29 | cmdQuerySession 30 | ) 31 | 32 | type sessionCMD struct { 33 | Type sessionCommandType 34 | User string 35 | Group string 36 | Nonce string 37 | Menu []string 38 | Session string 39 | Address string 40 | ResultChan chan SessionResult 41 | ErrorChan chan error 42 | } 43 | 44 | type SessionResult struct { 45 | Error error 46 | Session LoggedSession 47 | SessionList []LoggedSession 48 | } 49 | 50 | type SessionManager struct { 51 | sessions map[string]LoggedSession 52 | commands chan sessionCMD 53 | runner *framework.SimpleRunner 54 | } 55 | 56 | const ( 57 | DefaultSessionTimeout = 3 * time.Minute 58 | ) 59 | 60 | func CreateSessionManager() (manager *SessionManager, err error) { 61 | manager = &SessionManager{} 62 | manager.sessions = map[string]LoggedSession{} 63 | manager.commands = make(chan sessionCMD, 1<<10) 64 | manager.runner = framework.CreateSimpleRunner(manager.Routine) 65 | return 66 | } 67 | 68 | func (manager *SessionManager) Start() error{ 69 | return manager.runner.Start() 70 | } 71 | 72 | func (manager *SessionManager) Stop() error{ 73 | return manager.runner.Stop() 74 | } 75 | 76 | func (manager *SessionManager) Routine(c framework.RoutineController) { 77 | const ( 78 | TimerInterval = 10 * time.Second 79 | ) 80 | log.Println(" started") 81 | var ticker = time.NewTicker(TimerInterval) 82 | for !c.IsStopping() { 83 | select { 84 | case <-c.GetNotifyChannel(): 85 | c.SetStopping() 86 | case cmd := <-manager.commands: 87 | manager.handleCommand(cmd) 88 | case <-ticker.C: 89 | manager.checkTimeout() 90 | } 91 | } 92 | c.NotifyExit() 93 | log.Println(" stopped") 94 | } 95 | 96 | func (manager *SessionManager) AllocateSession(user, group, nonce, address string, menu []string, resp chan SessionResult) { 97 | manager.commands <- sessionCMD{Type: cmdAllocateSession, User: user, Group:group, Nonce: nonce, Address: address, Menu: menu, ResultChan: resp} 98 | } 99 | 100 | func (manager *SessionManager) UpdateSession(session string, resp chan error) { 101 | manager.commands <- sessionCMD{Type: cmdUpdateSession, Session: session, ErrorChan: resp} 102 | } 103 | 104 | func (manager *SessionManager) GetSession(session string, resp chan SessionResult) { 105 | manager.commands <- sessionCMD{Type: cmdGetSession, Session: session, ResultChan: resp} 106 | } 107 | 108 | func (manager *SessionManager) QuerySessions(resp chan SessionResult) { 109 | manager.commands <- sessionCMD{Type: cmdQuerySession, ResultChan: resp} 110 | } 111 | 112 | func (manager *SessionManager) handleCommand(cmd sessionCMD) { 113 | var err error 114 | switch cmd.Type { 115 | case cmdAllocateSession: 116 | err = manager.handleAllocateSession(cmd.User, cmd.Group, cmd.Nonce, cmd.Address, cmd.Menu, cmd.ResultChan) 117 | case cmdGetSession: 118 | err = manager.handleGetSession(cmd.Session, cmd.ResultChan) 119 | case cmdUpdateSession: 120 | err = manager.handleUpdateSession(cmd.Session, cmd.ErrorChan) 121 | case cmdQuerySession: 122 | err = manager.handleQuerySessions(cmd.ResultChan) 123 | default: 124 | log.Printf(" unsupport command type %d", cmd.Type) 125 | return 126 | } 127 | if err != nil{ 128 | log.Printf(" handle command type %d fail: %s", cmd.Type, err.Error()) 129 | } 130 | } 131 | 132 | func (manager *SessionManager) handleAllocateSession(user, group, nonce, address string, menu []string, resp chan SessionResult) (err error) { 133 | 134 | var session = LoggedSession{} 135 | var UID = uuid.NewV4() 136 | session.ID = UID.String() 137 | session.User = user 138 | session.Group = group 139 | session.Nonce = nonce 140 | session.Menu = menu 141 | session.Address = address 142 | session.Timeout = int(DefaultSessionTimeout / time.Second) 143 | session.Expire = time.Now().Add(DefaultSessionTimeout) 144 | manager.sessions[session.ID] = session 145 | resp <- SessionResult{Session:session} 146 | log.Printf(" new session '%s' allocated with user '%s.%s', remote address %s", session.ID, group, user, address) 147 | return nil 148 | } 149 | 150 | func (manager *SessionManager) handleUpdateSession(sessionID string, resp chan error) (err error) { 151 | session, exists := manager.sessions[sessionID] 152 | if !exists{ 153 | err = fmt.Errorf("invalid session '%s'", sessionID) 154 | resp <- err 155 | return err 156 | } 157 | session.Expire = time.Now().Add(DefaultSessionTimeout) 158 | manager.sessions[session.ID] = session 159 | resp <- nil 160 | return nil 161 | } 162 | 163 | func (manager *SessionManager) handleGetSession(sessionID string, resp chan SessionResult) (err error) { 164 | session, exists := manager.sessions[sessionID] 165 | if !exists{ 166 | err = fmt.Errorf("invalid session '%s'", sessionID) 167 | resp <- SessionResult{Error:err} 168 | return err 169 | } 170 | resp <- SessionResult{Session:session} 171 | return nil 172 | } 173 | 174 | func (manager *SessionManager) handleQuerySessions(resp chan SessionResult) (err error) { 175 | var result = make([]LoggedSession, 0) 176 | for _, session := range manager.sessions{ 177 | result = append(result, session) 178 | } 179 | resp <- SessionResult{SessionList:result} 180 | return nil 181 | } 182 | 183 | func (manager *SessionManager) checkTimeout(){ 184 | var now = time.Now() 185 | var timeoutList []string 186 | for id, session := range manager.sessions{ 187 | if session.Expire.Before(now){ 188 | timeoutList = append(timeoutList, id) 189 | } 190 | } 191 | for _, id := range timeoutList{ 192 | if _, exists := manager.sessions[id];exists{ 193 | delete(manager.sessions, id) 194 | log.Printf(" timeout session '%s' removed", id) 195 | } 196 | } 197 | } -------------------------------------------------------------------------------- /web_root/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/project-nano/frontend/18d3341c2dae56d42b1978ca87a0e6bcc4befbbe/web_root/apple-icon.png -------------------------------------------------------------------------------- /web_root/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "main.css": "/static/css/main.13f8852b.chunk.css", 4 | "main.js": "/static/js/main.9fd6f9c0.chunk.js", 5 | "main.js.map": "/static/js/main.9fd6f9c0.chunk.js.map", 6 | "runtime-main.js": "/static/js/runtime-main.412f7afe.js", 7 | "runtime-main.js.map": "/static/js/runtime-main.412f7afe.js.map", 8 | "static/css/2.d0176e96.chunk.css": "/static/css/2.d0176e96.chunk.css", 9 | "static/js/2.0693f478.chunk.js": "/static/js/2.0693f478.chunk.js", 10 | "static/js/2.0693f478.chunk.js.map": "/static/js/2.0693f478.chunk.js.map", 11 | "index.html": "/index.html", 12 | "precache-manifest.894bb590fa8ff419fafc579ff8334eb9.js": "/precache-manifest.894bb590fa8ff419fafc579ff8334eb9.js", 13 | "service-worker.js": "/service-worker.js", 14 | "static/css/2.d0176e96.chunk.css.map": "/static/css/2.d0176e96.chunk.css.map", 15 | "static/css/main.13f8852b.chunk.css.map": "/static/css/main.13f8852b.chunk.css.map", 16 | "static/js/2.0693f478.chunk.js.LICENSE.txt": "/static/js/2.0693f478.chunk.js.LICENSE.txt", 17 | "static/js/main.9fd6f9c0.chunk.js.LICENSE.txt": "/static/js/main.9fd6f9c0.chunk.js.LICENSE.txt", 18 | "static/media/login_background.jpg": "/static/media/login_background.327add31.jpg", 19 | "static/media/nano_white.svg": "/static/media/nano_white.ad11d364.svg", 20 | "static/media/sidebar.jpg": "/static/media/sidebar.18c01f03.jpg" 21 | }, 22 | "entrypoints": [ 23 | "static/js/runtime-main.412f7afe.js", 24 | "static/css/2.d0176e96.chunk.css", 25 | "static/js/2.0693f478.chunk.js", 26 | "static/css/main.13f8852b.chunk.css", 27 | "static/js/main.9fd6f9c0.chunk.js" 28 | ] 29 | } -------------------------------------------------------------------------------- /web_root/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/project-nano/frontend/18d3341c2dae56d42b1978ca87a0e6bcc4befbbe/web_root/favicon.ico -------------------------------------------------------------------------------- /web_root/index.html: -------------------------------------------------------------------------------- 1 | Project Nano
-------------------------------------------------------------------------------- /web_root/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /web_root/precache-manifest.1aff4a0c2475d969f0bdc6822387a682.js: -------------------------------------------------------------------------------- 1 | self.__precacheManifest = (self.__precacheManifest || []).concat([ 2 | { 3 | "revision": "e10f51e470a27c86bfa477d7363cb6da", 4 | "url": "/index.html" 5 | }, 6 | { 7 | "revision": "ba0dc42d2e0005ecd2ea", 8 | "url": "/static/css/2.d0176e96.chunk.css" 9 | }, 10 | { 11 | "revision": "f0b4d02c40539e75dfc1", 12 | "url": "/static/css/main.13f8852b.chunk.css" 13 | }, 14 | { 15 | "revision": "ba0dc42d2e0005ecd2ea", 16 | "url": "/static/js/2.ba83a81c.chunk.js" 17 | }, 18 | { 19 | "revision": "d5997dcc91f78c7c06537a574752f9b6", 20 | "url": "/static/js/2.ba83a81c.chunk.js.LICENSE.txt" 21 | }, 22 | { 23 | "revision": "f0b4d02c40539e75dfc1", 24 | "url": "/static/js/main.851f2e9f.chunk.js" 25 | }, 26 | { 27 | "revision": "7269b0c4207b44d8a9e4f0b85c8a81d2", 28 | "url": "/static/js/main.851f2e9f.chunk.js.LICENSE.txt" 29 | }, 30 | { 31 | "revision": "8c9cf417af9246fbcd0a", 32 | "url": "/static/js/runtime-main.412f7afe.js" 33 | }, 34 | { 35 | "revision": "327add3171a2a510744c77ad7bc6a1ec", 36 | "url": "/static/media/login_background.327add31.jpg" 37 | }, 38 | { 39 | "revision": "ad11d3643455ad1101433f10dec02756", 40 | "url": "/static/media/nano_white.ad11d364.svg" 41 | }, 42 | { 43 | "revision": "18c01f0307ed23da419c07106963d3a6", 44 | "url": "/static/media/sidebar.18c01f03.jpg" 45 | } 46 | ]); -------------------------------------------------------------------------------- /web_root/precache-manifest.4590baa3341561701ccab355dd1c9e60.js: -------------------------------------------------------------------------------- 1 | self.__precacheManifest = (self.__precacheManifest || []).concat([ 2 | { 3 | "revision": "87eb99c1bffd64a41f88c58b39e36456", 4 | "url": "/index.html" 5 | }, 6 | { 7 | "revision": "bb6dd6c5d9f5e088d4e1", 8 | "url": "/static/css/2.d0176e96.chunk.css" 9 | }, 10 | { 11 | "revision": "0616b2f6fba449e0be3c", 12 | "url": "/static/css/main.dfd6ecea.chunk.css" 13 | }, 14 | { 15 | "revision": "bb6dd6c5d9f5e088d4e1", 16 | "url": "/static/js/2.111defca.chunk.js" 17 | }, 18 | { 19 | "revision": "46144be5d7b2522996ff9715ad9364d4", 20 | "url": "/static/js/2.111defca.chunk.js.LICENSE.txt" 21 | }, 22 | { 23 | "revision": "0616b2f6fba449e0be3c", 24 | "url": "/static/js/main.8156b384.chunk.js" 25 | }, 26 | { 27 | "revision": "7269b0c4207b44d8a9e4f0b85c8a81d2", 28 | "url": "/static/js/main.8156b384.chunk.js.LICENSE.txt" 29 | }, 30 | { 31 | "revision": "8c9cf417af9246fbcd0a", 32 | "url": "/static/js/runtime-main.412f7afe.js" 33 | }, 34 | { 35 | "revision": "327add3171a2a510744c77ad7bc6a1ec", 36 | "url": "/static/media/login_background.327add31.jpg" 37 | }, 38 | { 39 | "revision": "a92c198a57008f53811eaaa320c38a3f", 40 | "url": "/static/media/nano_white.a92c198a.svg" 41 | }, 42 | { 43 | "revision": "18c01f0307ed23da419c07106963d3a6", 44 | "url": "/static/media/sidebar.18c01f03.jpg" 45 | } 46 | ]); -------------------------------------------------------------------------------- /web_root/precache-manifest.894bb590fa8ff419fafc579ff8334eb9.js: -------------------------------------------------------------------------------- 1 | self.__precacheManifest = (self.__precacheManifest || []).concat([ 2 | { 3 | "revision": "5864e315c40b38a7da46f2f1f91e75e1", 4 | "url": "/index.html" 5 | }, 6 | { 7 | "revision": "63e101492e93d9bafb51", 8 | "url": "/static/css/2.d0176e96.chunk.css" 9 | }, 10 | { 11 | "revision": "2aa3b706fe3710c60d77", 12 | "url": "/static/css/main.13f8852b.chunk.css" 13 | }, 14 | { 15 | "revision": "63e101492e93d9bafb51", 16 | "url": "/static/js/2.0693f478.chunk.js" 17 | }, 18 | { 19 | "revision": "d5997dcc91f78c7c06537a574752f9b6", 20 | "url": "/static/js/2.0693f478.chunk.js.LICENSE.txt" 21 | }, 22 | { 23 | "revision": "2aa3b706fe3710c60d77", 24 | "url": "/static/js/main.9fd6f9c0.chunk.js" 25 | }, 26 | { 27 | "revision": "7269b0c4207b44d8a9e4f0b85c8a81d2", 28 | "url": "/static/js/main.9fd6f9c0.chunk.js.LICENSE.txt" 29 | }, 30 | { 31 | "revision": "8c9cf417af9246fbcd0a", 32 | "url": "/static/js/runtime-main.412f7afe.js" 33 | }, 34 | { 35 | "revision": "327add3171a2a510744c77ad7bc6a1ec", 36 | "url": "/static/media/login_background.327add31.jpg" 37 | }, 38 | { 39 | "revision": "ad11d3643455ad1101433f10dec02756", 40 | "url": "/static/media/nano_white.ad11d364.svg" 41 | }, 42 | { 43 | "revision": "18c01f0307ed23da419c07106963d3a6", 44 | "url": "/static/media/sidebar.18c01f03.jpg" 45 | } 46 | ]); -------------------------------------------------------------------------------- /web_root/precache-manifest.f9bc75adcb5c0dff8eec578839242f95.js: -------------------------------------------------------------------------------- 1 | self.__precacheManifest = (self.__precacheManifest || []).concat([ 2 | { 3 | "revision": "b012d4302a898a4b0fe0e14e2fe54bba", 4 | "url": "/index.html" 5 | }, 6 | { 7 | "revision": "530d1656fc3a217bc076", 8 | "url": "/static/css/2.d0176e96.chunk.css" 9 | }, 10 | { 11 | "revision": "117f29b02ce32b16043c", 12 | "url": "/static/css/main.92970355.chunk.css" 13 | }, 14 | { 15 | "revision": "530d1656fc3a217bc076", 16 | "url": "/static/js/2.d8260731.chunk.js" 17 | }, 18 | { 19 | "revision": "48ac6c43b2c0f3418343885bd358714c", 20 | "url": "/static/js/2.d8260731.chunk.js.LICENSE.txt" 21 | }, 22 | { 23 | "revision": "117f29b02ce32b16043c", 24 | "url": "/static/js/main.2a00f055.chunk.js" 25 | }, 26 | { 27 | "revision": "7269b0c4207b44d8a9e4f0b85c8a81d2", 28 | "url": "/static/js/main.2a00f055.chunk.js.LICENSE.txt" 29 | }, 30 | { 31 | "revision": "5ecddd545b9a581d5ac3", 32 | "url": "/static/js/runtime-main.6ed7819b.js" 33 | }, 34 | { 35 | "revision": "327add3171a2a510744c77ad7bc6a1ec", 36 | "url": "/static/media/login_background.327add31.jpg" 37 | }, 38 | { 39 | "revision": "a92c198a57008f53811eaaa320c38a3f", 40 | "url": "/static/media/nano_white.a92c198a.svg" 41 | }, 42 | { 43 | "revision": "18c01f0307ed23da419c07106963d3a6", 44 | "url": "/static/media/sidebar.18c01f03.jpg" 45 | } 46 | ]); -------------------------------------------------------------------------------- /web_root/service-worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Welcome to your Workbox-powered service worker! 3 | * 4 | * You'll need to register this file in your web app and you should 5 | * disable HTTP caching for this file too. 6 | * See https://goo.gl/nhQhGp 7 | * 8 | * The rest of the code is auto-generated. Please don't update this file 9 | * directly; instead, make changes to your Workbox build configuration 10 | * and re-run your build process. 11 | * See https://goo.gl/2aRDsh 12 | */ 13 | 14 | importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js"); 15 | 16 | importScripts( 17 | "/precache-manifest.894bb590fa8ff419fafc579ff8334eb9.js" 18 | ); 19 | 20 | self.addEventListener('message', (event) => { 21 | if (event.data && event.data.type === 'SKIP_WAITING') { 22 | self.skipWaiting(); 23 | } 24 | }); 25 | 26 | workbox.core.clientsClaim(); 27 | 28 | /** 29 | * The workboxSW.precacheAndRoute() method efficiently caches and responds to 30 | * requests for URLs in the manifest. 31 | * See https://goo.gl/S9QRab 32 | */ 33 | self.__precacheManifest = [].concat(self.__precacheManifest || []); 34 | workbox.precaching.precacheAndRoute(self.__precacheManifest, {}); 35 | 36 | workbox.routing.registerNavigationRoute(workbox.precaching.getCacheKeyForURL("/index.html"), { 37 | 38 | blacklist: [/^\/_/,/\/[^/?]+\.[^/]+$/], 39 | }); 40 | -------------------------------------------------------------------------------- /web_root/static/css/2.d0176e96.chunk.css: -------------------------------------------------------------------------------- 1 | .ps{overflow:hidden!important;overflow-anchor:none;-ms-overflow-style:none;touch-action:auto;-ms-touch-action:auto}.ps__rail-x{height:15px;bottom:0}.ps__rail-x,.ps__rail-y{display:none;opacity:0;transition:background-color .2s linear,opacity .2s linear;-webkit-transition:background-color .2s linear,opacity .2s linear;position:absolute}.ps__rail-y{width:15px;right:0}.ps--active-x>.ps__rail-x,.ps--active-y>.ps__rail-y{display:block;background-color:initial}.ps--focus>.ps__rail-x,.ps--focus>.ps__rail-y,.ps--scrolling-x>.ps__rail-x,.ps--scrolling-y>.ps__rail-y,.ps:hover>.ps__rail-x,.ps:hover>.ps__rail-y{opacity:.6}.ps .ps__rail-x.ps--clicking,.ps .ps__rail-x:focus,.ps .ps__rail-x:hover,.ps .ps__rail-y.ps--clicking,.ps .ps__rail-y:focus,.ps .ps__rail-y:hover{background-color:#eee;opacity:.9}.ps__thumb-x{transition:background-color .2s linear,height .2s ease-in-out;-webkit-transition:background-color .2s linear,height .2s ease-in-out;height:6px;bottom:2px}.ps__thumb-x,.ps__thumb-y{background-color:#aaa;border-radius:6px;position:absolute}.ps__thumb-y{transition:background-color .2s linear,width .2s ease-in-out;-webkit-transition:background-color .2s linear,width .2s ease-in-out;width:6px;right:2px}.ps__rail-x.ps--clicking .ps__thumb-x,.ps__rail-x:focus>.ps__thumb-x,.ps__rail-x:hover>.ps__thumb-x{background-color:#999;height:11px}.ps__rail-y.ps--clicking .ps__thumb-y,.ps__rail-y:focus>.ps__thumb-y,.ps__rail-y:hover>.ps__thumb-y{background-color:#999;width:11px}@supports (-ms-overflow-style:none){.ps{overflow:auto!important}}@media (-ms-high-contrast:none),screen and (-ms-high-contrast:active){.ps{overflow:auto!important}} 2 | /*# sourceMappingURL=2.d0176e96.chunk.css.map */ -------------------------------------------------------------------------------- /web_root/static/css/2.d0176e96.chunk.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["perfect-scrollbar.css"],"names":[],"mappings":"AAGA,IACE,yBAA2B,CAC3B,oBAAqB,CACrB,uBAAwB,CACxB,iBAAkB,CAClB,qBACF,CAKA,YAKE,WAAY,CAEZ,QAGF,CAEA,wBAXE,YAAa,CACb,SAAU,CACV,yDAA2D,CAC3D,iEAAmE,CAKnE,iBAaF,CAVA,YAKE,UAAW,CAEX,OAGF,CAEA,oDAEE,aAAc,CACd,wBACF,CAEA,oJAME,UACF,CAEA,kJAME,qBAAsB,CACtB,UACF,CAKA,aAGE,6DAA+D,CAC/D,qEAAuE,CACvE,UAAW,CAEX,UAGF,CAEA,0BAXE,qBAAsB,CACtB,iBAAkB,CAOlB,iBAaF,CAVA,aAGE,4DAA8D,CAC9D,oEAAsE,CACtE,SAAU,CAEV,SAGF,CAEA,oGAGE,qBAAsB,CACtB,WACF,CAEA,oGAGE,qBAAsB,CACtB,UACF,CAGA,oCACE,IACE,uBACF,CACF,CAEA,sEACE,IACE,uBACF,CACF","file":"2.d0176e96.chunk.css","sourcesContent":["/*\n * Container style\n */\n.ps {\n overflow: hidden !important;\n overflow-anchor: none;\n -ms-overflow-style: none;\n touch-action: auto;\n -ms-touch-action: auto;\n}\n\n/*\n * Scrollbar rail styles\n */\n.ps__rail-x {\n display: none;\n opacity: 0;\n transition: background-color .2s linear, opacity .2s linear;\n -webkit-transition: background-color .2s linear, opacity .2s linear;\n height: 15px;\n /* there must be 'bottom' or 'top' for ps__rail-x */\n bottom: 0px;\n /* please don't change 'position' */\n position: absolute;\n}\n\n.ps__rail-y {\n display: none;\n opacity: 0;\n transition: background-color .2s linear, opacity .2s linear;\n -webkit-transition: background-color .2s linear, opacity .2s linear;\n width: 15px;\n /* there must be 'right' or 'left' for ps__rail-y */\n right: 0;\n /* please don't change 'position' */\n position: absolute;\n}\n\n.ps--active-x > .ps__rail-x,\n.ps--active-y > .ps__rail-y {\n display: block;\n background-color: transparent;\n}\n\n.ps:hover > .ps__rail-x,\n.ps:hover > .ps__rail-y,\n.ps--focus > .ps__rail-x,\n.ps--focus > .ps__rail-y,\n.ps--scrolling-x > .ps__rail-x,\n.ps--scrolling-y > .ps__rail-y {\n opacity: 0.6;\n}\n\n.ps .ps__rail-x:hover,\n.ps .ps__rail-y:hover,\n.ps .ps__rail-x:focus,\n.ps .ps__rail-y:focus,\n.ps .ps__rail-x.ps--clicking,\n.ps .ps__rail-y.ps--clicking {\n background-color: #eee;\n opacity: 0.9;\n}\n\n/*\n * Scrollbar thumb styles\n */\n.ps__thumb-x {\n background-color: #aaa;\n border-radius: 6px;\n transition: background-color .2s linear, height .2s ease-in-out;\n -webkit-transition: background-color .2s linear, height .2s ease-in-out;\n height: 6px;\n /* there must be 'bottom' for ps__thumb-x */\n bottom: 2px;\n /* please don't change 'position' */\n position: absolute;\n}\n\n.ps__thumb-y {\n background-color: #aaa;\n border-radius: 6px;\n transition: background-color .2s linear, width .2s ease-in-out;\n -webkit-transition: background-color .2s linear, width .2s ease-in-out;\n width: 6px;\n /* there must be 'right' for ps__thumb-y */\n right: 2px;\n /* please don't change 'position' */\n position: absolute;\n}\n\n.ps__rail-x:hover > .ps__thumb-x,\n.ps__rail-x:focus > .ps__thumb-x,\n.ps__rail-x.ps--clicking .ps__thumb-x {\n background-color: #999;\n height: 11px;\n}\n\n.ps__rail-y:hover > .ps__thumb-y,\n.ps__rail-y:focus > .ps__thumb-y,\n.ps__rail-y.ps--clicking .ps__thumb-y {\n background-color: #999;\n width: 11px;\n}\n\n/* MS supports */\n@supports (-ms-overflow-style: none) {\n .ps {\n overflow: auto !important;\n }\n}\n\n@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {\n .ps {\n overflow: auto !important;\n }\n}\n"]} -------------------------------------------------------------------------------- /web_root/static/css/main.13f8852b.chunk.css: -------------------------------------------------------------------------------- 1 | /*! 2 | 3 | ========================================================= 4 | * Material Dashboard React - v1.8.0 based on Material Dashboard - v1.2.0 5 | ========================================================= 6 | 7 | * Product Page: http://www.creative-tim.com/product/material-dashboard-react 8 | * Copyright 2019 Creative Tim (http://www.creative-tim.com) 9 | * Licensed under MIT (https://github.com/creativetimofficial/material-dashboard-react/blob/master/LICENSE.md) 10 | 11 | ========================================================= 12 | 13 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 14 | 15 | */.ct-grid{stroke:hsla(0,0%,100%,.2);stroke-width:1px;stroke-dasharray:2px}.ct-series-a .ct-bar,.ct-series-a .ct-line,.ct-series-a .ct-point,.ct-series-a .ct-slice-donut{stroke:hsla(0,0%,100%,.8)}.ct-label.ct-horizontal.ct-end{align-items:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-label{color:hsla(0,0%,100%,.7)}.ct-chart-bar .ct-label,.ct-chart-line .ct-label,.ct-chart-pie .ct-label{display:block;display:flex}.ct-label{fill:rgba(0,0,0,.4);line-height:1}html *{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{margin:0;font-family:Roboto,Helvetica,Arial,sans-serif;font-weight:300;line-height:1.5em}blockquote footer:before,blockquote small:before{content:"\2014 \00A0"}small{font-size:80%}h1{font-size:3em;line-height:1.15em}h2{font-size:2.4em}h3{font-size:1.825em;margin:20px 0 10px}h3,h4{line-height:1.4em}h4{font-size:1.3em}h5{font-size:1.25em;line-height:1.4em;margin-bottom:15px}h6{font-size:1em;text-transform:uppercase;font-weight:500}body{background-color:#eee;color:#3c4858}blockquote p{font-style:italic}body,h1,h2,h3,h4,h5,h6{font-family:"Roboto","Helvetica","Arial",sans-serif;font-weight:300;line-height:1.5em}a{color:#9c27b0}a,a:focus,a:hover{text-decoration:none}a:focus,a:hover{color:#89229b}legend{border-bottom:0}*{-webkit-tap-highlight-color:rgba(255,255,255,0);-webkit-tap-highlight-color:transparent}:focus{outline:0}a:active,a:focus,button::-moz-focus-inner,button:active,button:focus,button:hover,input[type=button]::-moz-focus-inner,input[type=file]>input[type=button]::-moz-focus-inner,input[type=reset]::-moz-focus-inner,input[type=submit]::-moz-focus-inner,select::-moz-focus-inner{outline:0!important}legend{margin-bottom:20px;font-size:21px}output{padding-top:8px}label,output{font-size:14px;line-height:1.42857}label{color:#aaa;font-weight:400}footer{padding:15px 0}footer ul{margin-bottom:0;padding:0;list-style:none}footer ul li{display:inline-block}footer ul li a{color:inherit;padding:15px;font-weight:500;font-size:12px;text-transform:uppercase;border-radius:3px;position:relative;display:block}footer ul li a,footer ul li a:hover{text-decoration:none}@media (max-width:991px){body,html{position:relative;overflow-x:hidden}#bodyClick{height:100%;width:100%;position:fixed;opacity:0;top:0;left:auto;right:260px;content:"";z-index:9999;overflow-x:hidden}}.fixed-plugin{font-family:"Roboto","Helvetica","Arial",sans-serif;font-weight:300;line-height:1.5em;position:fixed;top:180px;right:0;width:64px;background:rgba(0,0,0,.3);z-index:1031;border-radius:8px 0 0 8px;text-align:center;top:120px;.badge-primary-background-color:#9c27b0}.fixed-plugin .github-btn,.fixed-plugin .SocialMediaShareButton{display:inline-block}.fixed-plugin .badge,.fixed-plugin li>a{transition:all .34s;-webkit-transition:all .34s;-moz-transition:all .34s;text-decoration:none}.fixed-plugin .fa-cog{color:#fff;padding:10px;border-radius:0 0 6px 6px;width:auto}.fixed-plugin .dropdown-menu{right:80px;left:auto;width:290px;border-radius:.1875rem;padding:0 10px;position:absolute;color:rgba(0,0,0,.87);display:inline-block;box-shadow:0 1px 4px 0 rgba(0,0,0,.14);background:#fff;border-radius:3px}.fixed-plugin .fa-circle-thin{color:#fff}.fixed-plugin .active .fa-circle-thin{color:#0bf}.fixed-plugin .dropdown-menu>.active>a,.fixed-plugin .dropdown-menu>.active>a:focus,.fixed-plugin .dropdown-menu>.active>a:hover{color:#777;text-align:center}.fixed-plugin img{border-radius:0;width:100%;height:100px;margin:0 auto}.fixed-plugin .dropdown-menu li>a:focus,.fixed-plugin .dropdown-menu li>a:hover{box-shadow:none}.fixed-plugin .badge{border:3px solid #fff;border-radius:50%;cursor:pointer;display:inline-block;height:23px;margin-right:5px;position:relative;width:23px;background-color:rgba(30,30,30,.97)}.fixed-plugin .badge.active,.fixed-plugin .badge:hover{border-color:#0bf}.fixed-plugin .badge-purple{background-color:#9c27b0}.fixed-plugin .badge-blue{background-color:#00bcd4}.fixed-plugin .badge-green{background-color:#4caf50}.fixed-plugin .badge-orange{background-color:#ff9800}.fixed-plugin .badge-red{background-color:#f44336}.fixed-plugin h5{font-size:14px;margin:10px}.fixed-plugin .dropdown-menu li{display:block;padding:4px 0;width:25%;float:left}.fixed-plugin li.adjustments-line,.fixed-plugin li.button-container,.fixed-plugin li.header-title{width:100%;height:50px;min-height:inherit;padding:0;text-align:center}.fixed-plugin li.adjustments-line p{margin:0}.fixed-plugin li.adjustments-line div,.fixed-plugin li.button-container div,.fixed-plugin li.header-title div{margin-bottom:5px}.fixed-plugin li.header-title{height:30px;line-height:25px;font-size:12px;font-weight:600;text-align:center;text-transform:uppercase}.fixed-plugin .adjustments-line p{float:left;display:inline-block;margin-bottom:0;font-size:1em;color:#3c4858}.fixed-plugin .adjustments-line a{color:transparent}.fixed-plugin .adjustments-line a .badge-colors{position:relative;top:-2px}.fixed-plugin .adjustments-line a a:focus,.fixed-plugin .adjustments-line a a:hover{color:transparent}.fixed-plugin .adjustments-line .dropdown-menu>li.adjustments-line>a{padding-right:0;padding-left:0;border-bottom:1px solid #ddd;border-radius:0;margin:0}.fixed-plugin .dropdown-menu>li>a.img-holder{font-size:16px;text-align:center;border-radius:10px;background-color:#fff;border:3px solid #fff;opacity:1;cursor:pointer;display:block;max-height:100px;overflow:hidden;padding:0}.fixed-plugin .dropdown-menu>li>a.img-holder img{margin-top:auto}.fixed-plugin .dropdown-menu>li:focus>a.img-holder,.fixed-plugin .dropdown-menu>li:hover>a.img-holder{border-color:rgba(0,187,255,.53)}.fixed-plugin .dropdown-menu>.active>a.img-holder{border-color:#0bf;background-color:#fff}.fixed-plugin .dropdown .dropdown-menu{transform:translateY(-15%);top:27px;opacity:0;transform-origin:0 0;display:none}.fixed-plugin .dropdown .dropdown-menu:before{border-bottom:.4em solid transparent;border-left:.4em solid rgba(0,0,0,.2);border-top:.4em solid transparent;right:-16px;top:46px}.fixed-plugin .dropdown .dropdown-menu:after{border-bottom:.4em solid transparent;border-left:.4em solid #fff;border-top:.4em solid transparent;right:-16px}.fixed-plugin .dropdown .dropdown-menu:after,.fixed-plugin .dropdown .dropdown-menu:before{content:"";display:inline-block;position:absolute;top:46px;width:16px;transform:translateY(-50%);-webkit-transform:translateY(-50%);-moz-transform:translateY(-50%)}.fixed-plugin .dropdown.show .dropdown-menu{display:block;visibility:visible;opacity:1;transform:translateY(-13%);transform-origin:0 0}.fixed-plugin.rtl-fixed-plugin{right:auto;left:0;border-radius:0 8px 8px 0}.fixed-plugin.rtl-fixed-plugin .dropdown-menu{right:auto;left:80px}*{letter-spacing:normal!important} 16 | /*# sourceMappingURL=main.13f8852b.chunk.css.map */ -------------------------------------------------------------------------------- /web_root/static/css/main.13f8852b.chunk.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["material-dashboard-react.css"],"names":[],"mappings":"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","file":"main.13f8852b.chunk.css","sourcesContent":["/*!\n\n =========================================================\n * Material Dashboard React - v1.8.0 based on Material Dashboard - v1.2.0\n =========================================================\n\n * Product Page: http://www.creative-tim.com/product/material-dashboard-react\n * Copyright 2019 Creative Tim (http://www.creative-tim.com)\n * Licensed under MIT (https://github.com/creativetimofficial/material-dashboard-react/blob/master/LICENSE.md)\n\n =========================================================\n\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\n */\n.ct-grid {\n stroke: rgba(255, 255, 255, 0.2);\n stroke-width: 1px;\n stroke-dasharray: 2px;\n}\n\n.ct-series-a .ct-point,\n.ct-series-a .ct-line,\n.ct-series-a .ct-bar,\n.ct-series-a .ct-slice-donut {\n stroke: rgba(255, 255, 255, 0.8);\n}\n\n.ct-label.ct-horizontal.ct-end {\n -webkit-box-align: flex-start;\n -webkit-align-items: flex-start;\n -ms-flex-align: flex-start;\n align-items: flex-start;\n -webkit-box-pack: flex-start;\n -webkit-justify-content: flex-start;\n -ms-flex-pack: flex-start;\n justify-content: flex-start;\n text-align: left;\n text-anchor: start;\n}\n\n.ct-label {\n color: rgba(255, 255, 255, 0.7);\n}\n\n.ct-chart-line .ct-label,\n.ct-chart-bar .ct-label,\n.ct-chart-pie .ct-label {\n display: block;\n display: -webkit-box;\n display: -moz-box;\n display: -ms-flexbox;\n display: -webkit-flex;\n display: flex;\n}\n\n.ct-label {\n fill: rgba(0, 0, 0, 0.4);\n line-height: 1;\n}\nhtml * {\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\nbody {\n background-color: #eeeeee;\n color: #3c4858;\n margin: 0;\n font-family: Roboto, Helvetica, Arial, sans-serif;\n font-weight: 300;\n line-height: 1.5em;\n}\n\nblockquote footer:before,\nblockquote small:before {\n content: \"\\2014 \\00A0\";\n}\n\nsmall {\n font-size: 80%;\n}\n\nh1 {\n font-size: 3em;\n line-height: 1.15em;\n}\n\nh2 {\n font-size: 2.4em;\n}\n\nh3 {\n font-size: 1.825em;\n line-height: 1.4em;\n margin: 20px 0 10px;\n}\n\nh4 {\n font-size: 1.3em;\n line-height: 1.4em;\n}\n\nh5 {\n font-size: 1.25em;\n line-height: 1.4em;\n margin-bottom: 15px;\n}\n\nh6 {\n font-size: 1em;\n text-transform: uppercase;\n font-weight: 500;\n}\n\nbody {\n background-color: #eeeeee;\n color: #3c4858;\n}\n\nblockquote p {\n font-style: italic;\n}\n\nbody,\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n font-family: \"Roboto\", \"Helvetica\", \"Arial\", sans-serif;\n font-weight: 300;\n line-height: 1.5em;\n}\n\na {\n color: #9c27b0;\n text-decoration: none;\n}\n\na:hover,\na:focus {\n color: #89229b;\n text-decoration: none;\n}\n\nlegend {\n border-bottom: 0;\n}\n\n* {\n -webkit-tap-highlight-color: rgba(255, 255, 255, 0);\n -webkit-tap-highlight-color: transparent;\n}\n\n*:focus {\n outline: 0;\n}\n\na:focus,\na:active,\nbutton:active,\nbutton:focus,\nbutton:hover,\nbutton::-moz-focus-inner,\ninput[type=\"reset\"]::-moz-focus-inner,\ninput[type=\"button\"]::-moz-focus-inner,\ninput[type=\"submit\"]::-moz-focus-inner,\nselect::-moz-focus-inner,\ninput[type=\"file\"] > input[type=\"button\"]::-moz-focus-inner {\n outline: 0 !important;\n}\n\nlegend {\n margin-bottom: 20px;\n font-size: 21px;\n}\n\noutput {\n padding-top: 8px;\n font-size: 14px;\n line-height: 1.42857;\n}\n\nlabel {\n font-size: 14px;\n line-height: 1.42857;\n color: #aaaaaa;\n font-weight: 400;\n}\n\nfooter {\n padding: 15px 0;\n}\n\nfooter ul {\n margin-bottom: 0;\n padding: 0;\n list-style: none;\n}\n\nfooter ul li {\n display: inline-block;\n}\n\nfooter ul li a {\n color: inherit;\n padding: 15px;\n font-weight: 500;\n font-size: 12px;\n text-transform: uppercase;\n border-radius: 3px;\n text-decoration: none;\n position: relative;\n display: block;\n}\n\nfooter ul li a:hover {\n text-decoration: none;\n}\n\n@media (max-width: 991px) {\n body,\n html {\n position: relative;\n overflow-x: hidden;\n }\n\n #bodyClick {\n height: 100%;\n width: 100%;\n position: fixed;\n opacity: 0;\n top: 0;\n left: auto;\n right: 260px;\n content: \"\";\n z-index: 9999;\n overflow-x: hidden;\n }\n}\n.fixed-plugin {\n font-family: \"Roboto\", \"Helvetica\", \"Arial\", sans-serif;\n font-weight: 300;\n line-height: 1.5em;\n position: fixed;\n top: 180px;\n right: 0;\n width: 64px;\n background: rgba(0, 0, 0, 0.3);\n z-index: 1031;\n border-radius: 8px 0 0 8px;\n text-align: center;\n top: 120px;\n .badge-primary-background-color: #9c27b0;\n}\n\n.fixed-plugin .SocialMediaShareButton,\n.fixed-plugin .github-btn {\n display: inline-block;\n}\n\n.fixed-plugin li > a,\n.fixed-plugin .badge {\n transition: all 0.34s;\n -webkit-transition: all 0.34s;\n -moz-transition: all 0.34s;\n text-decoration: none;\n}\n\n.fixed-plugin .fa-cog {\n color: #ffffff;\n padding: 10px;\n border-radius: 0 0 6px 6px;\n width: auto;\n}\n\n.fixed-plugin .dropdown-menu {\n right: 80px;\n left: auto;\n width: 290px;\n border-radius: 0.1875rem;\n padding: 0 10px;\n position: absolute;\n color: rgba(0, 0, 0, 0.87);\n display: inline-block;\n box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.14);\n background: #fff;\n border-radius: 3px;\n}\n\n.fixed-plugin .fa-circle-thin {\n color: #ffffff;\n}\n\n.fixed-plugin .active .fa-circle-thin {\n color: #00bbff;\n}\n\n.fixed-plugin .dropdown-menu > .active > a,\n.fixed-plugin .dropdown-menu > .active > a:hover,\n.fixed-plugin .dropdown-menu > .active > a:focus {\n color: #777777;\n text-align: center;\n}\n\n.fixed-plugin img {\n border-radius: 0;\n width: 100%;\n height: 100px;\n margin: 0 auto;\n}\n\n.fixed-plugin .dropdown-menu li > a:hover,\n.fixed-plugin .dropdown-menu li > a:focus {\n box-shadow: none;\n}\n.fixed-plugin .badge {\n border: 3px solid #ffffff;\n border-radius: 50%;\n cursor: pointer;\n display: inline-block;\n height: 23px;\n margin-right: 5px;\n position: relative;\n width: 23px;\n background-color: rgba(30, 30, 30, 0.97);\n}\n\n.fixed-plugin .badge.active,\n.fixed-plugin .badge:hover {\n border-color: #00bbff;\n}\n\n.fixed-plugin .badge-purple {\n background-color: #9c27b0;\n}\n\n.fixed-plugin .badge-blue {\n background-color: #00bcd4;\n}\n\n.fixed-plugin .badge-green {\n background-color: #4caf50;\n}\n\n.fixed-plugin .badge-orange {\n background-color: #ff9800;\n}\n\n.fixed-plugin .badge-red {\n background-color: #f44336;\n}\n\n.fixed-plugin h5 {\n font-size: 14px;\n margin: 10px;\n}\n.fixed-plugin .dropdown-menu li {\n display: block;\n padding: 4px 0px;\n width: 25%;\n float: left;\n}\n\n.fixed-plugin li.adjustments-line,\n.fixed-plugin li.header-title,\n.fixed-plugin li.button-container {\n width: 100%;\n height: 50px;\n min-height: inherit;\n padding: 0px;\n text-align: center;\n}\n\n.fixed-plugin li.adjustments-line p {\n margin: 0;\n}\n\n.fixed-plugin li.adjustments-line div,\n.fixed-plugin li.header-title div,\n.fixed-plugin li.button-container div {\n margin-bottom: 5px;\n}\n.fixed-plugin li.header-title {\n height: 30px;\n line-height: 25px;\n font-size: 12px;\n font-weight: 600;\n text-align: center;\n text-transform: uppercase;\n}\n\n.fixed-plugin .adjustments-line p {\n float: left;\n display: inline-block;\n margin-bottom: 0;\n font-size: 1em;\n color: #3c4858;\n}\n\n.fixed-plugin .adjustments-line a {\n color: transparent;\n}\n\n.fixed-plugin .adjustments-line a .badge-colors {\n position: relative;\n top: -2px;\n}\n\n.fixed-plugin .adjustments-line a a:hover,\n.fixed-plugin .adjustments-line a a:focus {\n color: transparent;\n}\n.fixed-plugin .adjustments-line .dropdown-menu > li.adjustments-line > a {\n padding-right: 0;\n padding-left: 0;\n border-bottom: 1px solid #ddd;\n border-radius: 0;\n margin: 0;\n}\n\n.fixed-plugin .dropdown-menu > li > a.img-holder {\n font-size: 16px;\n text-align: center;\n border-radius: 10px;\n background-color: #fff;\n border: 3px solid #fff;\n padding-left: 0;\n padding-right: 0;\n opacity: 1;\n cursor: pointer;\n display: block;\n max-height: 100px;\n overflow: hidden;\n padding: 0;\n}\n\n.fixed-plugin .dropdown-menu > li > a.img-holder img {\n margin-top: auto;\n}\n.fixed-plugin .dropdown-menu > li:hover > a.img-holder,\n.fixed-plugin .dropdown-menu > li:focus > a.img-holder {\n border-color: rgba(0, 187, 255, 0.53);\n}\n\n.fixed-plugin .dropdown-menu > .active > a.img-holder,\n.fixed-plugin .dropdown-menu > .active > a.img-holder {\n border-color: #00bbff;\n background-color: #ffffff;\n}\n.fixed-plugin .dropdown .dropdown-menu {\n -webkit-transform: translateY(-15%);\n -moz-transform: translateY(-15%);\n -o-transform: translateY(-15%);\n -ms-transform: translateY(-15%);\n transform: translateY(-15%);\n top: 27px;\n opacity: 0;\n transform-origin: 0 0;\n display: none;\n}\n\n.fixed-plugin .dropdown .dropdown-menu:before {\n border-bottom: 0.4em solid transparent;\n border-left: 0.4em solid rgba(0, 0, 0, 0.2);\n border-top: 0.4em solid transparent;\n right: -16px;\n top: 46px;\n}\n.fixed-plugin .dropdown .dropdown-menu:after {\n border-bottom: 0.4em solid transparent;\n border-left: 0.4em solid #ffffff;\n border-top: 0.4em solid transparent;\n right: -16px;\n}\n\n.fixed-plugin .dropdown .dropdown-menu:before,\n.fixed-plugin .dropdown .dropdown-menu:after {\n content: \"\";\n display: inline-block;\n position: absolute;\n top: 46px;\n width: 16px;\n transform: translateY(-50%);\n -webkit-transform: translateY(-50%);\n -moz-transform: translateY(-50%);\n}\n\n.fixed-plugin .dropdown.show .dropdown-menu {\n display: block;\n visibility: visible;\n opacity: 1;\n -webkit-transform: translateY(-13%);\n -moz-transform: translateY(-13%);\n -o-transform: translateY(-13%);\n -ms-transform: translateY(-13%);\n transform: translateY(-13%);\n transform-origin: 0 0;\n}\n.fixed-plugin.rtl-fixed-plugin {\n right: auto;\n left: 0px;\n border-radius: 0 8px 8px 0;\n}\n.fixed-plugin.rtl-fixed-plugin .dropdown-menu {\n right: auto;\n left: 80px;\n}\n* {\n letter-spacing: normal !important;\n}\n"]} -------------------------------------------------------------------------------- /web_root/static/css/main.92970355.chunk.css: -------------------------------------------------------------------------------- 1 | /*! 2 | 3 | ========================================================= 4 | * Material Dashboard React - v1.8.0 based on Material Dashboard - v1.2.0 5 | ========================================================= 6 | 7 | * Product Page: http://www.creative-tim.com/product/material-dashboard-react 8 | * Copyright 2019 Creative Tim (http://www.creative-tim.com) 9 | * Licensed under MIT (https://github.com/creativetimofficial/material-dashboard-react/blob/master/LICENSE.md) 10 | 11 | ========================================================= 12 | 13 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 14 | 15 | */.ct-grid{stroke:hsla(0,0%,100%,.2);stroke-width:1px;stroke-dasharray:2px}.ct-series-a .ct-bar,.ct-series-a .ct-line,.ct-series-a .ct-point,.ct-series-a .ct-slice-donut{stroke:hsla(0,0%,100%,.8)}.ct-label.ct-horizontal.ct-end{align-items:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-label{color:hsla(0,0%,100%,.7)}.ct-chart-bar .ct-label,.ct-chart-line .ct-label,.ct-chart-pie .ct-label{display:block;display:flex}.ct-label{fill:rgba(0,0,0,.4);line-height:1}html *{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{margin:0;font-family:Roboto,Helvetica,Arial,sans-serif;font-weight:300;line-height:1.5em}blockquote footer:before,blockquote small:before{content:"\2014 \00A0"}small{font-size:80%}h1{font-size:3em;line-height:1.15em}h2{font-size:2.4em}h3{font-size:1.825em;margin:20px 0 10px}h3,h4{line-height:1.4em}h4{font-size:1.3em}h5{font-size:1.25em;line-height:1.4em;margin-bottom:15px}h6{font-size:1em;text-transform:uppercase;font-weight:500}body{background-color:#eee;color:#3c4858}blockquote p{font-style:italic}body,h1,h2,h3,h4,h5,h6{font-family:"Roboto","Helvetica","Arial",sans-serif;font-weight:300;line-height:1.5em}a{color:#9c27b0}a,a:focus,a:hover{text-decoration:none}a:focus,a:hover{color:#89229b}legend{border-bottom:0}*{-webkit-tap-highlight-color:rgba(255,255,255,0);-webkit-tap-highlight-color:transparent}:focus{outline:0}a:active,a:focus,button::-moz-focus-inner,button:active,button:focus,button:hover,input[type=button]::-moz-focus-inner,input[type=file]>input[type=button]::-moz-focus-inner,input[type=reset]::-moz-focus-inner,input[type=submit]::-moz-focus-inner,select::-moz-focus-inner{outline:0!important}legend{margin-bottom:20px;font-size:21px}output{padding-top:8px}label,output{font-size:14px;line-height:1.42857}label{color:#aaa;font-weight:400}footer{padding:15px 0}footer ul{margin-bottom:0;padding:0;list-style:none}footer ul li{display:inline-block}footer ul li a{color:inherit;padding:15px;font-weight:500;font-size:12px;text-transform:uppercase;border-radius:3px;position:relative;display:block}footer ul li a,footer ul li a:hover{text-decoration:none}@media (max-width:991px){body,html{position:relative;overflow-x:hidden}#bodyClick{height:100%;width:100%;position:fixed;opacity:0;top:0;left:auto;right:260px;content:"";z-index:9999;overflow-x:hidden}}.fixed-plugin{font-family:"Roboto","Helvetica","Arial",sans-serif;font-weight:300;line-height:1.5em;position:fixed;top:180px;right:0;width:64px;background:rgba(0,0,0,.3);z-index:1031;border-radius:8px 0 0 8px;text-align:center;top:120px;.badge-primary-background-color:#9c27b0}.fixed-plugin .github-btn,.fixed-plugin .SocialMediaShareButton{display:inline-block}.fixed-plugin .badge,.fixed-plugin li>a{transition:all .34s;-webkit-transition:all .34s;-moz-transition:all .34s;text-decoration:none}.fixed-plugin .fa-cog{color:#fff;padding:10px;border-radius:0 0 6px 6px;width:auto}.fixed-plugin .dropdown-menu{right:80px;left:auto;width:290px;border-radius:.1875rem;padding:0 10px;position:absolute;color:rgba(0,0,0,.87);display:inline-block;box-shadow:0 1px 4px 0 rgba(0,0,0,.14);background:#fff;border-radius:3px}.fixed-plugin .fa-circle-thin{color:#fff}.fixed-plugin .active .fa-circle-thin{color:#0bf}.fixed-plugin .dropdown-menu>.active>a,.fixed-plugin .dropdown-menu>.active>a:focus,.fixed-plugin .dropdown-menu>.active>a:hover{color:#777;text-align:center}.fixed-plugin img{border-radius:0;width:100%;height:100px;margin:0 auto}.fixed-plugin .dropdown-menu li>a:focus,.fixed-plugin .dropdown-menu li>a:hover{box-shadow:none}.fixed-plugin .badge{border:3px solid #fff;border-radius:50%;cursor:pointer;display:inline-block;height:23px;margin-right:5px;position:relative;width:23px;background-color:rgba(30,30,30,.97)}.fixed-plugin .badge.active,.fixed-plugin .badge:hover{border-color:#0bf}.fixed-plugin .badge-purple{background-color:#9c27b0}.fixed-plugin .badge-blue{background-color:#00bcd4}.fixed-plugin .badge-green{background-color:#4caf50}.fixed-plugin .badge-orange{background-color:#ff9800}.fixed-plugin .badge-red{background-color:#f44336}.fixed-plugin h5{font-size:14px;margin:10px}.fixed-plugin .dropdown-menu li{display:block;padding:4px 0;width:25%;float:left}.fixed-plugin li.adjustments-line,.fixed-plugin li.button-container,.fixed-plugin li.header-title{width:100%;height:50px;min-height:inherit;padding:0;text-align:center}.fixed-plugin li.adjustments-line p{margin:0}.fixed-plugin li.adjustments-line div,.fixed-plugin li.button-container div,.fixed-plugin li.header-title div{margin-bottom:5px}.fixed-plugin li.header-title{height:30px;line-height:25px;font-size:12px;font-weight:600;text-align:center;text-transform:uppercase}.fixed-plugin .adjustments-line p{float:left;display:inline-block;margin-bottom:0;font-size:1em;color:#3c4858}.fixed-plugin .adjustments-line a{color:transparent}.fixed-plugin .adjustments-line a .badge-colors{position:relative;top:-2px}.fixed-plugin .adjustments-line a a:focus,.fixed-plugin .adjustments-line a a:hover{color:transparent}.fixed-plugin .adjustments-line .dropdown-menu>li.adjustments-line>a{padding-right:0;padding-left:0;border-bottom:1px solid #ddd;border-radius:0;margin:0}.fixed-plugin .dropdown-menu>li>a.img-holder{font-size:16px;text-align:center;border-radius:10px;background-color:#fff;border:3px solid #fff;opacity:1;cursor:pointer;display:block;max-height:100px;overflow:hidden;padding:0}.fixed-plugin .dropdown-menu>li>a.img-holder img{margin-top:auto}.fixed-plugin .dropdown-menu>li:focus>a.img-holder,.fixed-plugin .dropdown-menu>li:hover>a.img-holder{border-color:rgba(0,187,255,.53)}.fixed-plugin .dropdown-menu>.active>a.img-holder{border-color:#0bf;background-color:#fff}.fixed-plugin .dropdown .dropdown-menu{transform:translateY(-15%);top:27px;opacity:0;transform-origin:0 0;display:none}.fixed-plugin .dropdown .dropdown-menu:before{border-bottom:.4em solid transparent;border-left:.4em solid rgba(0,0,0,.2);border-top:.4em solid transparent;right:-16px;top:46px}.fixed-plugin .dropdown .dropdown-menu:after{border-bottom:.4em solid transparent;border-left:.4em solid #fff;border-top:.4em solid transparent;right:-16px}.fixed-plugin .dropdown .dropdown-menu:after,.fixed-plugin .dropdown .dropdown-menu:before{content:"";display:inline-block;position:absolute;top:46px;width:16px;transform:translateY(-50%);-webkit-transform:translateY(-50%);-moz-transform:translateY(-50%)}.fixed-plugin .dropdown.show .dropdown-menu{display:block;visibility:visible;opacity:1;transform:translateY(-13%);transform-origin:0 0}.fixed-plugin.rtl-fixed-plugin{right:auto;left:0;border-radius:0 8px 8px 0}.fixed-plugin.rtl-fixed-plugin .dropdown-menu{right:auto;left:80px}*{letter-spacing:normal!important} 16 | /*# sourceMappingURL=main.92970355.chunk.css.map */ -------------------------------------------------------------------------------- /web_root/static/css/main.92970355.chunk.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["material-dashboard-react.css"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;EAcE,CACF,SACE,yBAAgC,CAChC,gBAAiB,CACjB,oBACF,CAEA,+FAIE,yBACF,CAEA,+BAIE,sBAAuB,CAIvB,0BAA2B,CAC3B,eAAgB,CAChB,iBACF,CAEA,UACE,wBACF,CAEA,yEAGE,aAAc,CAKd,YACF,CAEA,UACE,mBAAwB,CACxB,aACF,CACA,OACE,kCAAmC,CACnC,iCACF,CACA,KAGE,QAAS,CACT,6CAAiD,CACjD,eAAgB,CAChB,iBACF,CAEA,iDAEE,qBACF,CAEA,MACE,aACF,CAEA,GACE,aAAc,CACd,kBACF,CAEA,GACE,eACF,CAEA,GACE,iBAAkB,CAElB,kBACF,CAEA,MAJE,iBAOF,CAHA,GACE,eAEF,CAEA,GACE,gBAAiB,CACjB,iBAAkB,CAClB,kBACF,CAEA,GACE,aAAc,CACd,wBAAyB,CACzB,eACF,CAEA,KACE,qBAAyB,CACzB,aACF,CAEA,aACE,iBACF,CAEA,uBAOE,mDAAuD,CACvD,eAAgB,CAChB,iBACF,CAEA,EACE,aAEF,CAEA,kBAHE,oBAOF,CAJA,gBAEE,aAEF,CAEA,OACE,eACF,CAEA,EACE,+CAAmD,CACnD,uCACF,CAEA,OACE,SACF,CAEA,+QAWE,mBACF,CAEA,OACE,kBAAmB,CACnB,cACF,CAEA,OACE,eAGF,CAEA,aAJE,cAAe,CACf,mBAQF,CALA,MAGE,UAAc,CACd,eACF,CAEA,OACE,cACF,CAEA,UACE,eAAgB,CAChB,SAAU,CACV,eACF,CAEA,aACE,oBACF,CAEA,eACE,aAAc,CACd,YAAa,CACb,eAAgB,CAChB,cAAe,CACf,wBAAyB,CACzB,iBAAkB,CAElB,iBAAkB,CAClB,aACF,CAEA,oCALE,oBAOF,CAEA,yBACE,UAEE,iBAAkB,CAClB,iBACF,CAEA,WACE,WAAY,CACZ,UAAW,CACX,cAAe,CACf,SAAU,CACV,KAAM,CACN,SAAU,CACV,WAAY,CACZ,UAAW,CACX,YAAa,CACb,iBACF,CACF,CACA,cACE,mDAAuD,CACvD,eAAgB,CAChB,iBAAkB,CAClB,cAAe,CACf,SAAU,CACV,OAAQ,CACR,UAAW,CACX,yBAA8B,CAC9B,YAAa,CACb,yBAA0B,CAC1B,iBAAkB,CAClB,SAAU,CACV,uCACF,CAEA,gEAEE,oBACF,CAEA,wCAEE,mBAAqB,CACrB,2BAA6B,CAC7B,wBAA0B,CAC1B,oBACF,CAEA,sBACE,UAAc,CACd,YAAa,CACb,yBAA0B,CAC1B,UACF,CAEA,6BACE,UAAW,CACX,SAAU,CACV,WAAY,CACZ,sBAAwB,CACxB,cAAe,CACf,iBAAkB,CAClB,qBAA0B,CAC1B,oBAAqB,CACrB,sCAA2C,CAC3C,eAAgB,CAChB,iBACF,CAEA,8BACE,UACF,CAEA,sCACE,UACF,CAEA,iIAGE,UAAc,CACd,iBACF,CAEA,kBACE,eAAgB,CAChB,UAAW,CACX,YAAa,CACb,aACF,CAEA,gFAEE,eACF,CACA,qBACE,qBAAyB,CACzB,iBAAkB,CAClB,cAAe,CACf,oBAAqB,CACrB,WAAY,CACZ,gBAAiB,CACjB,iBAAkB,CAClB,UAAW,CACX,mCACF,CAEA,uDAEE,iBACF,CAEA,4BACE,wBACF,CAEA,0BACE,wBACF,CAEA,2BACE,wBACF,CAEA,4BACE,wBACF,CAEA,yBACE,wBACF,CAEA,iBACE,cAAe,CACf,WACF,CACA,gCACE,aAAc,CACd,aAAgB,CAChB,SAAU,CACV,UACF,CAEA,kGAGE,UAAW,CACX,WAAY,CACZ,kBAAmB,CACnB,SAAY,CACZ,iBACF,CAEA,oCACE,QACF,CAEA,8GAGE,iBACF,CACA,8BACE,WAAY,CACZ,gBAAiB,CACjB,cAAe,CACf,eAAgB,CAChB,iBAAkB,CAClB,wBACF,CAEA,kCACE,UAAW,CACX,oBAAqB,CACrB,eAAgB,CAChB,aAAc,CACd,aACF,CAEA,kCACE,iBACF,CAEA,gDACE,iBAAkB,CAClB,QACF,CAEA,oFAEE,iBACF,CACA,qEACE,eAAgB,CAChB,cAAe,CACf,4BAA6B,CAC7B,eAAgB,CAChB,QACF,CAEA,6CACE,cAAe,CACf,iBAAkB,CAClB,kBAAmB,CACnB,qBAAsB,CACtB,qBAAsB,CAGtB,SAAU,CACV,cAAe,CACf,aAAc,CACd,gBAAiB,CACjB,eAAgB,CAChB,SACF,CAEA,iDACE,eACF,CACA,sGAEE,gCACF,CAEA,kDAEE,iBAAqB,CACrB,qBACF,CACA,uCAKE,0BAA2B,CAC3B,QAAS,CACT,SAAU,CACV,oBAAqB,CACrB,YACF,CAEA,8CACE,oCAAsC,CACtC,qCAA2C,CAC3C,iCAAmC,CACnC,WAAY,CACZ,QACF,CACA,6CACE,oCAAsC,CACtC,2BAAgC,CAChC,iCAAmC,CACnC,WACF,CAEA,2FAEE,UAAW,CACX,oBAAqB,CACrB,iBAAkB,CAClB,QAAS,CACT,UAAW,CACX,0BAA2B,CAC3B,kCAAmC,CACnC,+BACF,CAEA,4CACE,aAAc,CACd,kBAAmB,CACnB,SAAU,CAKV,0BAA2B,CAC3B,oBACF,CACA,+BACE,UAAW,CACX,MAAS,CACT,yBACF,CACA,8CACE,UAAW,CACX,SACF,CACA,EACE,+BACF","file":"main.92970355.chunk.css","sourcesContent":["/*!\r\n\r\n =========================================================\r\n * Material Dashboard React - v1.8.0 based on Material Dashboard - v1.2.0\r\n =========================================================\r\n\r\n * Product Page: http://www.creative-tim.com/product/material-dashboard-react\r\n * Copyright 2019 Creative Tim (http://www.creative-tim.com)\r\n * Licensed under MIT (https://github.com/creativetimofficial/material-dashboard-react/blob/master/LICENSE.md)\r\n\r\n =========================================================\r\n\r\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\r\n\r\n */\r\n.ct-grid {\r\n stroke: rgba(255, 255, 255, 0.2);\r\n stroke-width: 1px;\r\n stroke-dasharray: 2px;\r\n}\r\n\r\n.ct-series-a .ct-point,\r\n.ct-series-a .ct-line,\r\n.ct-series-a .ct-bar,\r\n.ct-series-a .ct-slice-donut {\r\n stroke: rgba(255, 255, 255, 0.8);\r\n}\r\n\r\n.ct-label.ct-horizontal.ct-end {\r\n -webkit-box-align: flex-start;\r\n -webkit-align-items: flex-start;\r\n -ms-flex-align: flex-start;\r\n align-items: flex-start;\r\n -webkit-box-pack: flex-start;\r\n -webkit-justify-content: flex-start;\r\n -ms-flex-pack: flex-start;\r\n justify-content: flex-start;\r\n text-align: left;\r\n text-anchor: start;\r\n}\r\n\r\n.ct-label {\r\n color: rgba(255, 255, 255, 0.7);\r\n}\r\n\r\n.ct-chart-line .ct-label,\r\n.ct-chart-bar .ct-label,\r\n.ct-chart-pie .ct-label {\r\n display: block;\r\n display: -webkit-box;\r\n display: -moz-box;\r\n display: -ms-flexbox;\r\n display: -webkit-flex;\r\n display: flex;\r\n}\r\n\r\n.ct-label {\r\n fill: rgba(0, 0, 0, 0.4);\r\n line-height: 1;\r\n}\r\nhtml * {\r\n -webkit-font-smoothing: antialiased;\r\n -moz-osx-font-smoothing: grayscale;\r\n}\r\nbody {\r\n background-color: #eeeeee;\r\n color: #3c4858;\r\n margin: 0;\r\n font-family: Roboto, Helvetica, Arial, sans-serif;\r\n font-weight: 300;\r\n line-height: 1.5em;\r\n}\r\n\r\nblockquote footer:before,\r\nblockquote small:before {\r\n content: \"\\2014 \\00A0\";\r\n}\r\n\r\nsmall {\r\n font-size: 80%;\r\n}\r\n\r\nh1 {\r\n font-size: 3em;\r\n line-height: 1.15em;\r\n}\r\n\r\nh2 {\r\n font-size: 2.4em;\r\n}\r\n\r\nh3 {\r\n font-size: 1.825em;\r\n line-height: 1.4em;\r\n margin: 20px 0 10px;\r\n}\r\n\r\nh4 {\r\n font-size: 1.3em;\r\n line-height: 1.4em;\r\n}\r\n\r\nh5 {\r\n font-size: 1.25em;\r\n line-height: 1.4em;\r\n margin-bottom: 15px;\r\n}\r\n\r\nh6 {\r\n font-size: 1em;\r\n text-transform: uppercase;\r\n font-weight: 500;\r\n}\r\n\r\nbody {\r\n background-color: #eeeeee;\r\n color: #3c4858;\r\n}\r\n\r\nblockquote p {\r\n font-style: italic;\r\n}\r\n\r\nbody,\r\nh1,\r\nh2,\r\nh3,\r\nh4,\r\nh5,\r\nh6 {\r\n font-family: \"Roboto\", \"Helvetica\", \"Arial\", sans-serif;\r\n font-weight: 300;\r\n line-height: 1.5em;\r\n}\r\n\r\na {\r\n color: #9c27b0;\r\n text-decoration: none;\r\n}\r\n\r\na:hover,\r\na:focus {\r\n color: #89229b;\r\n text-decoration: none;\r\n}\r\n\r\nlegend {\r\n border-bottom: 0;\r\n}\r\n\r\n* {\r\n -webkit-tap-highlight-color: rgba(255, 255, 255, 0);\r\n -webkit-tap-highlight-color: transparent;\r\n}\r\n\r\n*:focus {\r\n outline: 0;\r\n}\r\n\r\na:focus,\r\na:active,\r\nbutton:active,\r\nbutton:focus,\r\nbutton:hover,\r\nbutton::-moz-focus-inner,\r\ninput[type=\"reset\"]::-moz-focus-inner,\r\ninput[type=\"button\"]::-moz-focus-inner,\r\ninput[type=\"submit\"]::-moz-focus-inner,\r\nselect::-moz-focus-inner,\r\ninput[type=\"file\"] > input[type=\"button\"]::-moz-focus-inner {\r\n outline: 0 !important;\r\n}\r\n\r\nlegend {\r\n margin-bottom: 20px;\r\n font-size: 21px;\r\n}\r\n\r\noutput {\r\n padding-top: 8px;\r\n font-size: 14px;\r\n line-height: 1.42857;\r\n}\r\n\r\nlabel {\r\n font-size: 14px;\r\n line-height: 1.42857;\r\n color: #aaaaaa;\r\n font-weight: 400;\r\n}\r\n\r\nfooter {\r\n padding: 15px 0;\r\n}\r\n\r\nfooter ul {\r\n margin-bottom: 0;\r\n padding: 0;\r\n list-style: none;\r\n}\r\n\r\nfooter ul li {\r\n display: inline-block;\r\n}\r\n\r\nfooter ul li a {\r\n color: inherit;\r\n padding: 15px;\r\n font-weight: 500;\r\n font-size: 12px;\r\n text-transform: uppercase;\r\n border-radius: 3px;\r\n text-decoration: none;\r\n position: relative;\r\n display: block;\r\n}\r\n\r\nfooter ul li a:hover {\r\n text-decoration: none;\r\n}\r\n\r\n@media (max-width: 991px) {\r\n body,\r\n html {\r\n position: relative;\r\n overflow-x: hidden;\r\n }\r\n\r\n #bodyClick {\r\n height: 100%;\r\n width: 100%;\r\n position: fixed;\r\n opacity: 0;\r\n top: 0;\r\n left: auto;\r\n right: 260px;\r\n content: \"\";\r\n z-index: 9999;\r\n overflow-x: hidden;\r\n }\r\n}\r\n.fixed-plugin {\r\n font-family: \"Roboto\", \"Helvetica\", \"Arial\", sans-serif;\r\n font-weight: 300;\r\n line-height: 1.5em;\r\n position: fixed;\r\n top: 180px;\r\n right: 0;\r\n width: 64px;\r\n background: rgba(0, 0, 0, 0.3);\r\n z-index: 1031;\r\n border-radius: 8px 0 0 8px;\r\n text-align: center;\r\n top: 120px;\r\n .badge-primary-background-color: #9c27b0;\r\n}\r\n\r\n.fixed-plugin .SocialMediaShareButton,\r\n.fixed-plugin .github-btn {\r\n display: inline-block;\r\n}\r\n\r\n.fixed-plugin li > a,\r\n.fixed-plugin .badge {\r\n transition: all 0.34s;\r\n -webkit-transition: all 0.34s;\r\n -moz-transition: all 0.34s;\r\n text-decoration: none;\r\n}\r\n\r\n.fixed-plugin .fa-cog {\r\n color: #ffffff;\r\n padding: 10px;\r\n border-radius: 0 0 6px 6px;\r\n width: auto;\r\n}\r\n\r\n.fixed-plugin .dropdown-menu {\r\n right: 80px;\r\n left: auto;\r\n width: 290px;\r\n border-radius: 0.1875rem;\r\n padding: 0 10px;\r\n position: absolute;\r\n color: rgba(0, 0, 0, 0.87);\r\n display: inline-block;\r\n box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.14);\r\n background: #fff;\r\n border-radius: 3px;\r\n}\r\n\r\n.fixed-plugin .fa-circle-thin {\r\n color: #ffffff;\r\n}\r\n\r\n.fixed-plugin .active .fa-circle-thin {\r\n color: #00bbff;\r\n}\r\n\r\n.fixed-plugin .dropdown-menu > .active > a,\r\n.fixed-plugin .dropdown-menu > .active > a:hover,\r\n.fixed-plugin .dropdown-menu > .active > a:focus {\r\n color: #777777;\r\n text-align: center;\r\n}\r\n\r\n.fixed-plugin img {\r\n border-radius: 0;\r\n width: 100%;\r\n height: 100px;\r\n margin: 0 auto;\r\n}\r\n\r\n.fixed-plugin .dropdown-menu li > a:hover,\r\n.fixed-plugin .dropdown-menu li > a:focus {\r\n box-shadow: none;\r\n}\r\n.fixed-plugin .badge {\r\n border: 3px solid #ffffff;\r\n border-radius: 50%;\r\n cursor: pointer;\r\n display: inline-block;\r\n height: 23px;\r\n margin-right: 5px;\r\n position: relative;\r\n width: 23px;\r\n background-color: rgba(30, 30, 30, 0.97);\r\n}\r\n\r\n.fixed-plugin .badge.active,\r\n.fixed-plugin .badge:hover {\r\n border-color: #00bbff;\r\n}\r\n\r\n.fixed-plugin .badge-purple {\r\n background-color: #9c27b0;\r\n}\r\n\r\n.fixed-plugin .badge-blue {\r\n background-color: #00bcd4;\r\n}\r\n\r\n.fixed-plugin .badge-green {\r\n background-color: #4caf50;\r\n}\r\n\r\n.fixed-plugin .badge-orange {\r\n background-color: #ff9800;\r\n}\r\n\r\n.fixed-plugin .badge-red {\r\n background-color: #f44336;\r\n}\r\n\r\n.fixed-plugin h5 {\r\n font-size: 14px;\r\n margin: 10px;\r\n}\r\n.fixed-plugin .dropdown-menu li {\r\n display: block;\r\n padding: 4px 0px;\r\n width: 25%;\r\n float: left;\r\n}\r\n\r\n.fixed-plugin li.adjustments-line,\r\n.fixed-plugin li.header-title,\r\n.fixed-plugin li.button-container {\r\n width: 100%;\r\n height: 50px;\r\n min-height: inherit;\r\n padding: 0px;\r\n text-align: center;\r\n}\r\n\r\n.fixed-plugin li.adjustments-line p {\r\n margin: 0;\r\n}\r\n\r\n.fixed-plugin li.adjustments-line div,\r\n.fixed-plugin li.header-title div,\r\n.fixed-plugin li.button-container div {\r\n margin-bottom: 5px;\r\n}\r\n.fixed-plugin li.header-title {\r\n height: 30px;\r\n line-height: 25px;\r\n font-size: 12px;\r\n font-weight: 600;\r\n text-align: center;\r\n text-transform: uppercase;\r\n}\r\n\r\n.fixed-plugin .adjustments-line p {\r\n float: left;\r\n display: inline-block;\r\n margin-bottom: 0;\r\n font-size: 1em;\r\n color: #3c4858;\r\n}\r\n\r\n.fixed-plugin .adjustments-line a {\r\n color: transparent;\r\n}\r\n\r\n.fixed-plugin .adjustments-line a .badge-colors {\r\n position: relative;\r\n top: -2px;\r\n}\r\n\r\n.fixed-plugin .adjustments-line a a:hover,\r\n.fixed-plugin .adjustments-line a a:focus {\r\n color: transparent;\r\n}\r\n.fixed-plugin .adjustments-line .dropdown-menu > li.adjustments-line > a {\r\n padding-right: 0;\r\n padding-left: 0;\r\n border-bottom: 1px solid #ddd;\r\n border-radius: 0;\r\n margin: 0;\r\n}\r\n\r\n.fixed-plugin .dropdown-menu > li > a.img-holder {\r\n font-size: 16px;\r\n text-align: center;\r\n border-radius: 10px;\r\n background-color: #fff;\r\n border: 3px solid #fff;\r\n padding-left: 0;\r\n padding-right: 0;\r\n opacity: 1;\r\n cursor: pointer;\r\n display: block;\r\n max-height: 100px;\r\n overflow: hidden;\r\n padding: 0;\r\n}\r\n\r\n.fixed-plugin .dropdown-menu > li > a.img-holder img {\r\n margin-top: auto;\r\n}\r\n.fixed-plugin .dropdown-menu > li:hover > a.img-holder,\r\n.fixed-plugin .dropdown-menu > li:focus > a.img-holder {\r\n border-color: rgba(0, 187, 255, 0.53);\r\n}\r\n\r\n.fixed-plugin .dropdown-menu > .active > a.img-holder,\r\n.fixed-plugin .dropdown-menu > .active > a.img-holder {\r\n border-color: #00bbff;\r\n background-color: #ffffff;\r\n}\r\n.fixed-plugin .dropdown .dropdown-menu {\r\n -webkit-transform: translateY(-15%);\r\n -moz-transform: translateY(-15%);\r\n -o-transform: translateY(-15%);\r\n -ms-transform: translateY(-15%);\r\n transform: translateY(-15%);\r\n top: 27px;\r\n opacity: 0;\r\n transform-origin: 0 0;\r\n display: none;\r\n}\r\n\r\n.fixed-plugin .dropdown .dropdown-menu:before {\r\n border-bottom: 0.4em solid transparent;\r\n border-left: 0.4em solid rgba(0, 0, 0, 0.2);\r\n border-top: 0.4em solid transparent;\r\n right: -16px;\r\n top: 46px;\r\n}\r\n.fixed-plugin .dropdown .dropdown-menu:after {\r\n border-bottom: 0.4em solid transparent;\r\n border-left: 0.4em solid #ffffff;\r\n border-top: 0.4em solid transparent;\r\n right: -16px;\r\n}\r\n\r\n.fixed-plugin .dropdown .dropdown-menu:before,\r\n.fixed-plugin .dropdown .dropdown-menu:after {\r\n content: \"\";\r\n display: inline-block;\r\n position: absolute;\r\n top: 46px;\r\n width: 16px;\r\n transform: translateY(-50%);\r\n -webkit-transform: translateY(-50%);\r\n -moz-transform: translateY(-50%);\r\n}\r\n\r\n.fixed-plugin .dropdown.show .dropdown-menu {\r\n display: block;\r\n visibility: visible;\r\n opacity: 1;\r\n -webkit-transform: translateY(-13%);\r\n -moz-transform: translateY(-13%);\r\n -o-transform: translateY(-13%);\r\n -ms-transform: translateY(-13%);\r\n transform: translateY(-13%);\r\n transform-origin: 0 0;\r\n}\r\n.fixed-plugin.rtl-fixed-plugin {\r\n right: auto;\r\n left: 0px;\r\n border-radius: 0 8px 8px 0;\r\n}\r\n.fixed-plugin.rtl-fixed-plugin .dropdown-menu {\r\n right: auto;\r\n left: 80px;\r\n}\r\n* {\r\n letter-spacing: normal !important;\r\n}\r\n"]} -------------------------------------------------------------------------------- /web_root/static/css/main.dfd6ecea.chunk.css: -------------------------------------------------------------------------------- 1 | /*! 2 | 3 | ========================================================= 4 | * Material Dashboard React - v1.8.0 based on Material Dashboard - v1.2.0 5 | ========================================================= 6 | 7 | * Product Page: http://www.creative-tim.com/product/material-dashboard-react 8 | * Copyright 2019 Creative Tim (http://www.creative-tim.com) 9 | * Licensed under MIT (https://github.com/creativetimofficial/material-dashboard-react/blob/master/LICENSE.md) 10 | 11 | ========================================================= 12 | 13 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 14 | 15 | */.ct-grid{stroke:hsla(0,0%,100%,.2);stroke-width:1px;stroke-dasharray:2px}.ct-series-a .ct-bar,.ct-series-a .ct-line,.ct-series-a .ct-point,.ct-series-a .ct-slice-donut{stroke:hsla(0,0%,100%,.8)}.ct-label.ct-horizontal.ct-end{align-items:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-label{color:hsla(0,0%,100%,.7)}.ct-chart-bar .ct-label,.ct-chart-line .ct-label,.ct-chart-pie .ct-label{display:block;display:flex}.ct-label{fill:rgba(0,0,0,.4);line-height:1}html *{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{margin:0;font-family:Roboto,Helvetica,Arial,sans-serif;font-weight:300;line-height:1.5em}blockquote footer:before,blockquote small:before{content:"\2014 \00A0"}small{font-size:80%}h1{font-size:3em;line-height:1.15em}h2{font-size:2.4em}h3{font-size:1.825em;margin:20px 0 10px}h3,h4{line-height:1.4em}h4{font-size:1.3em}h5{font-size:1.25em;line-height:1.4em;margin-bottom:15px}h6{font-size:1em;text-transform:uppercase;font-weight:500}body{background-color:#eee;color:#3c4858}blockquote p{font-style:italic}body,h1,h2,h3,h4,h5,h6{font-family:"Roboto","Helvetica","Arial",sans-serif;font-weight:300;line-height:1.5em}a{color:#9c27b0}a,a:focus,a:hover{text-decoration:none}a:focus,a:hover{color:#89229b}legend{border-bottom:0}*{-webkit-tap-highlight-color:rgba(255,255,255,0);-webkit-tap-highlight-color:transparent}:focus{outline:0}a:active,a:focus,button::-moz-focus-inner,button:active,button:focus,button:hover,input[type=button]::-moz-focus-inner,input[type=file]>input[type=button]::-moz-focus-inner,input[type=reset]::-moz-focus-inner,input[type=submit]::-moz-focus-inner,select::-moz-focus-inner{outline:0!important}legend{margin-bottom:20px;font-size:21px}output{padding-top:8px}label,output{font-size:14px;line-height:1.42857}label{color:#aaa;font-weight:400}footer{padding:15px 0}footer ul{margin-bottom:0;padding:0;list-style:none}footer ul li{display:inline-block}footer ul li a{color:inherit;padding:15px;font-weight:500;font-size:12px;text-transform:uppercase;border-radius:3px;position:relative;display:block}footer ul li a,footer ul li a:hover{text-decoration:none}@media (max-width:991px){body,html{position:relative;overflow-x:hidden}#bodyClick{height:100%;width:100%;position:fixed;opacity:0;top:0;left:auto;right:260px;content:"";z-index:9999;overflow-x:hidden}}.fixed-plugin{font-family:"Roboto","Helvetica","Arial",sans-serif;font-weight:300;line-height:1.5em;position:fixed;top:180px;right:0;width:64px;background:rgba(0,0,0,.3);z-index:1031;border-radius:8px 0 0 8px;text-align:center;top:120px;.badge-primary-background-color:#9c27b0}.fixed-plugin .github-btn,.fixed-plugin .SocialMediaShareButton{display:inline-block}.fixed-plugin .badge,.fixed-plugin li>a{transition:all .34s;-webkit-transition:all .34s;-moz-transition:all .34s;text-decoration:none}.fixed-plugin .fa-cog{color:#fff;padding:10px;border-radius:0 0 6px 6px;width:auto}.fixed-plugin .dropdown-menu{right:80px;left:auto;width:290px;border-radius:.1875rem;padding:0 10px;position:absolute;color:rgba(0,0,0,.87);display:inline-block;box-shadow:0 1px 4px 0 rgba(0,0,0,.14);background:#fff;border-radius:3px}.fixed-plugin .fa-circle-thin{color:#fff}.fixed-plugin .active .fa-circle-thin{color:#0bf}.fixed-plugin .dropdown-menu>.active>a,.fixed-plugin .dropdown-menu>.active>a:focus,.fixed-plugin .dropdown-menu>.active>a:hover{color:#777;text-align:center}.fixed-plugin img{border-radius:0;width:100%;height:100px;margin:0 auto}.fixed-plugin .dropdown-menu li>a:focus,.fixed-plugin .dropdown-menu li>a:hover{box-shadow:none}.fixed-plugin .badge{border:3px solid #fff;border-radius:50%;cursor:pointer;display:inline-block;height:23px;margin-right:5px;position:relative;width:23px;background-color:rgba(30,30,30,.97)}.fixed-plugin .badge.active,.fixed-plugin .badge:hover{border-color:#0bf}.fixed-plugin .badge-purple{background-color:#9c27b0}.fixed-plugin .badge-blue{background-color:#00bcd4}.fixed-plugin .badge-green{background-color:#4caf50}.fixed-plugin .badge-orange{background-color:#ff9800}.fixed-plugin .badge-red{background-color:#f44336}.fixed-plugin h5{font-size:14px;margin:10px}.fixed-plugin .dropdown-menu li{display:block;padding:4px 0;width:25%;float:left}.fixed-plugin li.adjustments-line,.fixed-plugin li.button-container,.fixed-plugin li.header-title{width:100%;height:50px;min-height:inherit;padding:0;text-align:center}.fixed-plugin li.adjustments-line p{margin:0}.fixed-plugin li.adjustments-line div,.fixed-plugin li.button-container div,.fixed-plugin li.header-title div{margin-bottom:5px}.fixed-plugin li.header-title{height:30px;line-height:25px;font-size:12px;font-weight:600;text-align:center;text-transform:uppercase}.fixed-plugin .adjustments-line p{float:left;display:inline-block;margin-bottom:0;font-size:1em;color:#3c4858}.fixed-plugin .adjustments-line a{color:transparent}.fixed-plugin .adjustments-line a .badge-colors{position:relative;top:-2px}.fixed-plugin .adjustments-line a a:focus,.fixed-plugin .adjustments-line a a:hover{color:transparent}.fixed-plugin .adjustments-line .dropdown-menu>li.adjustments-line>a{padding-right:0;padding-left:0;border-bottom:1px solid #ddd;border-radius:0;margin:0}.fixed-plugin .dropdown-menu>li>a.img-holder{font-size:16px;text-align:center;border-radius:10px;background-color:#fff;border:3px solid #fff;opacity:1;cursor:pointer;display:block;max-height:100px;overflow:hidden;padding:0}.fixed-plugin .dropdown-menu>li>a.img-holder img{margin-top:auto}.fixed-plugin .dropdown-menu>li:focus>a.img-holder,.fixed-plugin .dropdown-menu>li:hover>a.img-holder{border-color:rgba(0,187,255,.53)}.fixed-plugin .dropdown-menu>.active>a.img-holder{border-color:#0bf;background-color:#fff}.fixed-plugin .dropdown .dropdown-menu{-webkit-transform:translateY(-15%);transform:translateY(-15%);top:27px;opacity:0;-webkit-transform-origin:0 0;transform-origin:0 0;display:none}.fixed-plugin .dropdown .dropdown-menu:before{border-bottom:.4em solid transparent;border-left:.4em solid rgba(0,0,0,.2);border-top:.4em solid transparent;right:-16px;top:46px}.fixed-plugin .dropdown .dropdown-menu:after{border-bottom:.4em solid transparent;border-left:.4em solid #fff;border-top:.4em solid transparent;right:-16px}.fixed-plugin .dropdown .dropdown-menu:after,.fixed-plugin .dropdown .dropdown-menu:before{content:"";display:inline-block;position:absolute;top:46px;width:16px;transform:translateY(-50%);-webkit-transform:translateY(-50%);-moz-transform:translateY(-50%)}.fixed-plugin .dropdown.show .dropdown-menu{display:block;visibility:visible;opacity:1;-webkit-transform:translateY(-13%);transform:translateY(-13%);-webkit-transform-origin:0 0;transform-origin:0 0}.fixed-plugin.rtl-fixed-plugin{right:auto;left:0;border-radius:0 8px 8px 0}.fixed-plugin.rtl-fixed-plugin .dropdown-menu{right:auto;left:80px}*{letter-spacing:normal!important} 16 | /*# sourceMappingURL=main.dfd6ecea.chunk.css.map */ -------------------------------------------------------------------------------- /web_root/static/css/main.dfd6ecea.chunk.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["material-dashboard-react.css"],"names":[],"mappings":"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","file":"main.dfd6ecea.chunk.css","sourcesContent":["/*!\r\n\r\n =========================================================\r\n * Material Dashboard React - v1.8.0 based on Material Dashboard - v1.2.0\r\n =========================================================\r\n\r\n * Product Page: http://www.creative-tim.com/product/material-dashboard-react\r\n * Copyright 2019 Creative Tim (http://www.creative-tim.com)\r\n * Licensed under MIT (https://github.com/creativetimofficial/material-dashboard-react/blob/master/LICENSE.md)\r\n\r\n =========================================================\r\n\r\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\r\n\r\n */\r\n.ct-grid {\r\n stroke: rgba(255, 255, 255, 0.2);\r\n stroke-width: 1px;\r\n stroke-dasharray: 2px;\r\n}\r\n\r\n.ct-series-a .ct-point,\r\n.ct-series-a .ct-line,\r\n.ct-series-a .ct-bar,\r\n.ct-series-a .ct-slice-donut {\r\n stroke: rgba(255, 255, 255, 0.8);\r\n}\r\n\r\n.ct-label.ct-horizontal.ct-end {\r\n -webkit-box-align: flex-start;\r\n -webkit-align-items: flex-start;\r\n -ms-flex-align: flex-start;\r\n align-items: flex-start;\r\n -webkit-box-pack: flex-start;\r\n -webkit-justify-content: flex-start;\r\n -ms-flex-pack: flex-start;\r\n justify-content: flex-start;\r\n text-align: left;\r\n text-anchor: start;\r\n}\r\n\r\n.ct-label {\r\n color: rgba(255, 255, 255, 0.7);\r\n}\r\n\r\n.ct-chart-line .ct-label,\r\n.ct-chart-bar .ct-label,\r\n.ct-chart-pie .ct-label {\r\n display: block;\r\n display: -webkit-box;\r\n display: -moz-box;\r\n display: -ms-flexbox;\r\n display: -webkit-flex;\r\n display: flex;\r\n}\r\n\r\n.ct-label {\r\n fill: rgba(0, 0, 0, 0.4);\r\n line-height: 1;\r\n}\r\nhtml * {\r\n -webkit-font-smoothing: antialiased;\r\n -moz-osx-font-smoothing: grayscale;\r\n}\r\nbody {\r\n background-color: #eeeeee;\r\n color: #3c4858;\r\n margin: 0;\r\n font-family: Roboto, Helvetica, Arial, sans-serif;\r\n font-weight: 300;\r\n line-height: 1.5em;\r\n}\r\n\r\nblockquote footer:before,\r\nblockquote small:before {\r\n content: \"\\2014 \\00A0\";\r\n}\r\n\r\nsmall {\r\n font-size: 80%;\r\n}\r\n\r\nh1 {\r\n font-size: 3em;\r\n line-height: 1.15em;\r\n}\r\n\r\nh2 {\r\n font-size: 2.4em;\r\n}\r\n\r\nh3 {\r\n font-size: 1.825em;\r\n line-height: 1.4em;\r\n margin: 20px 0 10px;\r\n}\r\n\r\nh4 {\r\n font-size: 1.3em;\r\n line-height: 1.4em;\r\n}\r\n\r\nh5 {\r\n font-size: 1.25em;\r\n line-height: 1.4em;\r\n margin-bottom: 15px;\r\n}\r\n\r\nh6 {\r\n font-size: 1em;\r\n text-transform: uppercase;\r\n font-weight: 500;\r\n}\r\n\r\nbody {\r\n background-color: #eeeeee;\r\n color: #3c4858;\r\n}\r\n\r\nblockquote p {\r\n font-style: italic;\r\n}\r\n\r\nbody,\r\nh1,\r\nh2,\r\nh3,\r\nh4,\r\nh5,\r\nh6 {\r\n font-family: \"Roboto\", \"Helvetica\", \"Arial\", sans-serif;\r\n font-weight: 300;\r\n line-height: 1.5em;\r\n}\r\n\r\na {\r\n color: #9c27b0;\r\n text-decoration: none;\r\n}\r\n\r\na:hover,\r\na:focus {\r\n color: #89229b;\r\n text-decoration: none;\r\n}\r\n\r\nlegend {\r\n border-bottom: 0;\r\n}\r\n\r\n* {\r\n -webkit-tap-highlight-color: rgba(255, 255, 255, 0);\r\n -webkit-tap-highlight-color: transparent;\r\n}\r\n\r\n*:focus {\r\n outline: 0;\r\n}\r\n\r\na:focus,\r\na:active,\r\nbutton:active,\r\nbutton:focus,\r\nbutton:hover,\r\nbutton::-moz-focus-inner,\r\ninput[type=\"reset\"]::-moz-focus-inner,\r\ninput[type=\"button\"]::-moz-focus-inner,\r\ninput[type=\"submit\"]::-moz-focus-inner,\r\nselect::-moz-focus-inner,\r\ninput[type=\"file\"] > input[type=\"button\"]::-moz-focus-inner {\r\n outline: 0 !important;\r\n}\r\n\r\nlegend {\r\n margin-bottom: 20px;\r\n font-size: 21px;\r\n}\r\n\r\noutput {\r\n padding-top: 8px;\r\n font-size: 14px;\r\n line-height: 1.42857;\r\n}\r\n\r\nlabel {\r\n font-size: 14px;\r\n line-height: 1.42857;\r\n color: #aaaaaa;\r\n font-weight: 400;\r\n}\r\n\r\nfooter {\r\n padding: 15px 0;\r\n}\r\n\r\nfooter ul {\r\n margin-bottom: 0;\r\n padding: 0;\r\n list-style: none;\r\n}\r\n\r\nfooter ul li {\r\n display: inline-block;\r\n}\r\n\r\nfooter ul li a {\r\n color: inherit;\r\n padding: 15px;\r\n font-weight: 500;\r\n font-size: 12px;\r\n text-transform: uppercase;\r\n border-radius: 3px;\r\n text-decoration: none;\r\n position: relative;\r\n display: block;\r\n}\r\n\r\nfooter ul li a:hover {\r\n text-decoration: none;\r\n}\r\n\r\n@media (max-width: 991px) {\r\n body,\r\n html {\r\n position: relative;\r\n overflow-x: hidden;\r\n }\r\n\r\n #bodyClick {\r\n height: 100%;\r\n width: 100%;\r\n position: fixed;\r\n opacity: 0;\r\n top: 0;\r\n left: auto;\r\n right: 260px;\r\n content: \"\";\r\n z-index: 9999;\r\n overflow-x: hidden;\r\n }\r\n}\r\n.fixed-plugin {\r\n font-family: \"Roboto\", \"Helvetica\", \"Arial\", sans-serif;\r\n font-weight: 300;\r\n line-height: 1.5em;\r\n position: fixed;\r\n top: 180px;\r\n right: 0;\r\n width: 64px;\r\n background: rgba(0, 0, 0, 0.3);\r\n z-index: 1031;\r\n border-radius: 8px 0 0 8px;\r\n text-align: center;\r\n top: 120px;\r\n .badge-primary-background-color: #9c27b0;\r\n}\r\n\r\n.fixed-plugin .SocialMediaShareButton,\r\n.fixed-plugin .github-btn {\r\n display: inline-block;\r\n}\r\n\r\n.fixed-plugin li > a,\r\n.fixed-plugin .badge {\r\n transition: all 0.34s;\r\n -webkit-transition: all 0.34s;\r\n -moz-transition: all 0.34s;\r\n text-decoration: none;\r\n}\r\n\r\n.fixed-plugin .fa-cog {\r\n color: #ffffff;\r\n padding: 10px;\r\n border-radius: 0 0 6px 6px;\r\n width: auto;\r\n}\r\n\r\n.fixed-plugin .dropdown-menu {\r\n right: 80px;\r\n left: auto;\r\n width: 290px;\r\n border-radius: 0.1875rem;\r\n padding: 0 10px;\r\n position: absolute;\r\n color: rgba(0, 0, 0, 0.87);\r\n display: inline-block;\r\n box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.14);\r\n background: #fff;\r\n border-radius: 3px;\r\n}\r\n\r\n.fixed-plugin .fa-circle-thin {\r\n color: #ffffff;\r\n}\r\n\r\n.fixed-plugin .active .fa-circle-thin {\r\n color: #00bbff;\r\n}\r\n\r\n.fixed-plugin .dropdown-menu > .active > a,\r\n.fixed-plugin .dropdown-menu > .active > a:hover,\r\n.fixed-plugin .dropdown-menu > .active > a:focus {\r\n color: #777777;\r\n text-align: center;\r\n}\r\n\r\n.fixed-plugin img {\r\n border-radius: 0;\r\n width: 100%;\r\n height: 100px;\r\n margin: 0 auto;\r\n}\r\n\r\n.fixed-plugin .dropdown-menu li > a:hover,\r\n.fixed-plugin .dropdown-menu li > a:focus {\r\n box-shadow: none;\r\n}\r\n.fixed-plugin .badge {\r\n border: 3px solid #ffffff;\r\n border-radius: 50%;\r\n cursor: pointer;\r\n display: inline-block;\r\n height: 23px;\r\n margin-right: 5px;\r\n position: relative;\r\n width: 23px;\r\n background-color: rgba(30, 30, 30, 0.97);\r\n}\r\n\r\n.fixed-plugin .badge.active,\r\n.fixed-plugin .badge:hover {\r\n border-color: #00bbff;\r\n}\r\n\r\n.fixed-plugin .badge-purple {\r\n background-color: #9c27b0;\r\n}\r\n\r\n.fixed-plugin .badge-blue {\r\n background-color: #00bcd4;\r\n}\r\n\r\n.fixed-plugin .badge-green {\r\n background-color: #4caf50;\r\n}\r\n\r\n.fixed-plugin .badge-orange {\r\n background-color: #ff9800;\r\n}\r\n\r\n.fixed-plugin .badge-red {\r\n background-color: #f44336;\r\n}\r\n\r\n.fixed-plugin h5 {\r\n font-size: 14px;\r\n margin: 10px;\r\n}\r\n.fixed-plugin .dropdown-menu li {\r\n display: block;\r\n padding: 4px 0px;\r\n width: 25%;\r\n float: left;\r\n}\r\n\r\n.fixed-plugin li.adjustments-line,\r\n.fixed-plugin li.header-title,\r\n.fixed-plugin li.button-container {\r\n width: 100%;\r\n height: 50px;\r\n min-height: inherit;\r\n padding: 0px;\r\n text-align: center;\r\n}\r\n\r\n.fixed-plugin li.adjustments-line p {\r\n margin: 0;\r\n}\r\n\r\n.fixed-plugin li.adjustments-line div,\r\n.fixed-plugin li.header-title div,\r\n.fixed-plugin li.button-container div {\r\n margin-bottom: 5px;\r\n}\r\n.fixed-plugin li.header-title {\r\n height: 30px;\r\n line-height: 25px;\r\n font-size: 12px;\r\n font-weight: 600;\r\n text-align: center;\r\n text-transform: uppercase;\r\n}\r\n\r\n.fixed-plugin .adjustments-line p {\r\n float: left;\r\n display: inline-block;\r\n margin-bottom: 0;\r\n font-size: 1em;\r\n color: #3c4858;\r\n}\r\n\r\n.fixed-plugin .adjustments-line a {\r\n color: transparent;\r\n}\r\n\r\n.fixed-plugin .adjustments-line a .badge-colors {\r\n position: relative;\r\n top: -2px;\r\n}\r\n\r\n.fixed-plugin .adjustments-line a a:hover,\r\n.fixed-plugin .adjustments-line a a:focus {\r\n color: transparent;\r\n}\r\n.fixed-plugin .adjustments-line .dropdown-menu > li.adjustments-line > a {\r\n padding-right: 0;\r\n padding-left: 0;\r\n border-bottom: 1px solid #ddd;\r\n border-radius: 0;\r\n margin: 0;\r\n}\r\n\r\n.fixed-plugin .dropdown-menu > li > a.img-holder {\r\n font-size: 16px;\r\n text-align: center;\r\n border-radius: 10px;\r\n background-color: #fff;\r\n border: 3px solid #fff;\r\n padding-left: 0;\r\n padding-right: 0;\r\n opacity: 1;\r\n cursor: pointer;\r\n display: block;\r\n max-height: 100px;\r\n overflow: hidden;\r\n padding: 0;\r\n}\r\n\r\n.fixed-plugin .dropdown-menu > li > a.img-holder img {\r\n margin-top: auto;\r\n}\r\n.fixed-plugin .dropdown-menu > li:hover > a.img-holder,\r\n.fixed-plugin .dropdown-menu > li:focus > a.img-holder {\r\n border-color: rgba(0, 187, 255, 0.53);\r\n}\r\n\r\n.fixed-plugin .dropdown-menu > .active > a.img-holder,\r\n.fixed-plugin .dropdown-menu > .active > a.img-holder {\r\n border-color: #00bbff;\r\n background-color: #ffffff;\r\n}\r\n.fixed-plugin .dropdown .dropdown-menu {\r\n -webkit-transform: translateY(-15%);\r\n -moz-transform: translateY(-15%);\r\n -o-transform: translateY(-15%);\r\n -ms-transform: translateY(-15%);\r\n transform: translateY(-15%);\r\n top: 27px;\r\n opacity: 0;\r\n transform-origin: 0 0;\r\n display: none;\r\n}\r\n\r\n.fixed-plugin .dropdown .dropdown-menu:before {\r\n border-bottom: 0.4em solid transparent;\r\n border-left: 0.4em solid rgba(0, 0, 0, 0.2);\r\n border-top: 0.4em solid transparent;\r\n right: -16px;\r\n top: 46px;\r\n}\r\n.fixed-plugin .dropdown .dropdown-menu:after {\r\n border-bottom: 0.4em solid transparent;\r\n border-left: 0.4em solid #ffffff;\r\n border-top: 0.4em solid transparent;\r\n right: -16px;\r\n}\r\n\r\n.fixed-plugin .dropdown .dropdown-menu:before,\r\n.fixed-plugin .dropdown .dropdown-menu:after {\r\n content: \"\";\r\n display: inline-block;\r\n position: absolute;\r\n top: 46px;\r\n width: 16px;\r\n transform: translateY(-50%);\r\n -webkit-transform: translateY(-50%);\r\n -moz-transform: translateY(-50%);\r\n}\r\n\r\n.fixed-plugin .dropdown.show .dropdown-menu {\r\n display: block;\r\n visibility: visible;\r\n opacity: 1;\r\n -webkit-transform: translateY(-13%);\r\n -moz-transform: translateY(-13%);\r\n -o-transform: translateY(-13%);\r\n -ms-transform: translateY(-13%);\r\n transform: translateY(-13%);\r\n transform-origin: 0 0;\r\n}\r\n.fixed-plugin.rtl-fixed-plugin {\r\n right: auto;\r\n left: 0px;\r\n border-radius: 0 8px 8px 0;\r\n}\r\n.fixed-plugin.rtl-fixed-plugin .dropdown-menu {\r\n right: auto;\r\n left: 80px;\r\n}\r\n* {\r\n letter-spacing: normal !important;\r\n}\r\n"]} -------------------------------------------------------------------------------- /web_root/static/js/2.0693f478.chunk.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | object-assign 3 | (c) Sindre Sorhus 4 | @license MIT 5 | */ 6 | 7 | /*! 8 | Copyright (c) 2018 Jed Watson. 9 | Licensed under the MIT License (MIT), see 10 | http://jedwatson.github.io/classnames 11 | */ 12 | 13 | /*! 14 | * Chart.js v2.9.4 15 | * https://www.chartjs.org 16 | * (c) 2020 Chart.js Contributors 17 | * Released under the MIT License 18 | */ 19 | 20 | /*! 21 | * perfect-scrollbar v1.5.3 22 | * Copyright 2021 Hyunje Jun, MDBootstrap and Contributors 23 | * Licensed under MIT 24 | */ 25 | 26 | /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ 27 | 28 | /** 29 | * A better abstraction over CSS. 30 | * 31 | * @copyright Oleg Isonen (Slobodskoi) / Isonen 2014-present 32 | * @website https://github.com/cssinjs/jss 33 | * @license MIT 34 | */ 35 | 36 | /** @license React v0.19.1 37 | * scheduler.production.min.js 38 | * 39 | * Copyright (c) Facebook, Inc. and its affiliates. 40 | * 41 | * This source code is licensed under the MIT license found in the 42 | * LICENSE file in the root directory of this source tree. 43 | */ 44 | 45 | /** @license React v16.13.1 46 | * react-is.production.min.js 47 | * 48 | * Copyright (c) Facebook, Inc. and its affiliates. 49 | * 50 | * This source code is licensed under the MIT license found in the 51 | * LICENSE file in the root directory of this source tree. 52 | */ 53 | 54 | /** @license React v16.14.0 55 | * react-dom.production.min.js 56 | * 57 | * Copyright (c) Facebook, Inc. and its affiliates. 58 | * 59 | * This source code is licensed under the MIT license found in the 60 | * LICENSE file in the root directory of this source tree. 61 | */ 62 | 63 | /** @license React v16.14.0 64 | * react.production.min.js 65 | * 66 | * Copyright (c) Facebook, Inc. and its affiliates. 67 | * 68 | * This source code is licensed under the MIT license found in the 69 | * LICENSE file in the root directory of this source tree. 70 | */ 71 | 72 | /** @license React v17.0.2 73 | * react-is.production.min.js 74 | * 75 | * Copyright (c) Facebook, Inc. and its affiliates. 76 | * 77 | * This source code is licensed under the MIT license found in the 78 | * LICENSE file in the root directory of this source tree. 79 | */ 80 | 81 | /**! 82 | * @fileOverview Kickass library to create and place poppers near their reference elements. 83 | * @version 1.16.1-lts 84 | * @license 85 | * Copyright (c) 2016 Federico Zivolo and contributors 86 | * 87 | * Permission is hereby granted, free of charge, to any person obtaining a copy 88 | * of this software and associated documentation files (the "Software"), to deal 89 | * in the Software without restriction, including without limitation the rights 90 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 91 | * copies of the Software, and to permit persons to whom the Software is 92 | * furnished to do so, subject to the following conditions: 93 | * 94 | * The above copyright notice and this permission notice shall be included in all 95 | * copies or substantial portions of the Software. 96 | * 97 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 98 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 99 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 100 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 101 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 102 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 103 | * SOFTWARE. 104 | */ 105 | 106 | //! moment.js 107 | -------------------------------------------------------------------------------- /web_root/static/js/2.111defca.chunk.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | object-assign 3 | (c) Sindre Sorhus 4 | @license MIT 5 | */ 6 | 7 | /*! 8 | Copyright (c) 2018 Jed Watson. 9 | Licensed under the MIT License (MIT), see 10 | http://jedwatson.github.io/classnames 11 | */ 12 | 13 | /*! 14 | * @kurkle/color v0.2.1 15 | * https://github.com/kurkle/color#readme 16 | * (c) 2022 Jukka Kurkela 17 | * Released under the MIT License 18 | */ 19 | 20 | /*! 21 | * Chart.js v3.9.1 22 | * https://www.chartjs.org 23 | * (c) 2022 Chart.js Contributors 24 | * Released under the MIT License 25 | */ 26 | 27 | /*! 28 | * The buffer module from node.js, for the browser. 29 | * 30 | * @author Feross Aboukhadijeh 31 | * @license MIT 32 | */ 33 | 34 | /*! 35 | * perfect-scrollbar v1.5.3 36 | * Copyright 2021 Hyunje Jun, MDBootstrap and Contributors 37 | * Licensed under MIT 38 | */ 39 | 40 | /*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh */ 41 | 42 | /** 43 | * A better abstraction over CSS. 44 | * 45 | * @copyright Oleg Isonen (Slobodskoi) / Isonen 2014-present 46 | * @website https://github.com/cssinjs/jss 47 | * @license MIT 48 | */ 49 | 50 | /** @license React v0.19.1 51 | * scheduler.production.min.js 52 | * 53 | * Copyright (c) Facebook, Inc. and its affiliates. 54 | * 55 | * This source code is licensed under the MIT license found in the 56 | * LICENSE file in the root directory of this source tree. 57 | */ 58 | 59 | /** @license React v16.13.1 60 | * react-is.production.min.js 61 | * 62 | * Copyright (c) Facebook, Inc. and its affiliates. 63 | * 64 | * This source code is licensed under the MIT license found in the 65 | * LICENSE file in the root directory of this source tree. 66 | */ 67 | 68 | /** @license React v16.14.0 69 | * react-dom.production.min.js 70 | * 71 | * Copyright (c) Facebook, Inc. and its affiliates. 72 | * 73 | * This source code is licensed under the MIT license found in the 74 | * LICENSE file in the root directory of this source tree. 75 | */ 76 | 77 | /** @license React v16.14.0 78 | * react.production.min.js 79 | * 80 | * Copyright (c) Facebook, Inc. and its affiliates. 81 | * 82 | * This source code is licensed under the MIT license found in the 83 | * LICENSE file in the root directory of this source tree. 84 | */ 85 | 86 | /** @license React v17.0.2 87 | * react-is.production.min.js 88 | * 89 | * Copyright (c) Facebook, Inc. and its affiliates. 90 | * 91 | * This source code is licensed under the MIT license found in the 92 | * LICENSE file in the root directory of this source tree. 93 | */ 94 | 95 | /**! 96 | * @fileOverview Kickass library to create and place poppers near their reference elements. 97 | * @version 1.16.1-lts 98 | * @license 99 | * Copyright (c) 2016 Federico Zivolo and contributors 100 | * 101 | * Permission is hereby granted, free of charge, to any person obtaining a copy 102 | * of this software and associated documentation files (the "Software"), to deal 103 | * in the Software without restriction, including without limitation the rights 104 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 105 | * copies of the Software, and to permit persons to whom the Software is 106 | * furnished to do so, subject to the following conditions: 107 | * 108 | * The above copyright notice and this permission notice shall be included in all 109 | * copies or substantial portions of the Software. 110 | * 111 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 112 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 113 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 114 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 115 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 116 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 117 | * SOFTWARE. 118 | */ 119 | -------------------------------------------------------------------------------- /web_root/static/js/2.ba83a81c.chunk.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | object-assign 3 | (c) Sindre Sorhus 4 | @license MIT 5 | */ 6 | 7 | /*! 8 | Copyright (c) 2018 Jed Watson. 9 | Licensed under the MIT License (MIT), see 10 | http://jedwatson.github.io/classnames 11 | */ 12 | 13 | /*! 14 | * Chart.js v2.9.4 15 | * https://www.chartjs.org 16 | * (c) 2020 Chart.js Contributors 17 | * Released under the MIT License 18 | */ 19 | 20 | /*! 21 | * perfect-scrollbar v1.5.3 22 | * Copyright 2021 Hyunje Jun, MDBootstrap and Contributors 23 | * Licensed under MIT 24 | */ 25 | 26 | /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ 27 | 28 | /** 29 | * A better abstraction over CSS. 30 | * 31 | * @copyright Oleg Isonen (Slobodskoi) / Isonen 2014-present 32 | * @website https://github.com/cssinjs/jss 33 | * @license MIT 34 | */ 35 | 36 | /** @license React v0.19.1 37 | * scheduler.production.min.js 38 | * 39 | * Copyright (c) Facebook, Inc. and its affiliates. 40 | * 41 | * This source code is licensed under the MIT license found in the 42 | * LICENSE file in the root directory of this source tree. 43 | */ 44 | 45 | /** @license React v16.13.1 46 | * react-is.production.min.js 47 | * 48 | * Copyright (c) Facebook, Inc. and its affiliates. 49 | * 50 | * This source code is licensed under the MIT license found in the 51 | * LICENSE file in the root directory of this source tree. 52 | */ 53 | 54 | /** @license React v16.14.0 55 | * react-dom.production.min.js 56 | * 57 | * Copyright (c) Facebook, Inc. and its affiliates. 58 | * 59 | * This source code is licensed under the MIT license found in the 60 | * LICENSE file in the root directory of this source tree. 61 | */ 62 | 63 | /** @license React v16.14.0 64 | * react.production.min.js 65 | * 66 | * Copyright (c) Facebook, Inc. and its affiliates. 67 | * 68 | * This source code is licensed under the MIT license found in the 69 | * LICENSE file in the root directory of this source tree. 70 | */ 71 | 72 | /** @license React v17.0.2 73 | * react-is.production.min.js 74 | * 75 | * Copyright (c) Facebook, Inc. and its affiliates. 76 | * 77 | * This source code is licensed under the MIT license found in the 78 | * LICENSE file in the root directory of this source tree. 79 | */ 80 | 81 | /**! 82 | * @fileOverview Kickass library to create and place poppers near their reference elements. 83 | * @version 1.16.1-lts 84 | * @license 85 | * Copyright (c) 2016 Federico Zivolo and contributors 86 | * 87 | * Permission is hereby granted, free of charge, to any person obtaining a copy 88 | * of this software and associated documentation files (the "Software"), to deal 89 | * in the Software without restriction, including without limitation the rights 90 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 91 | * copies of the Software, and to permit persons to whom the Software is 92 | * furnished to do so, subject to the following conditions: 93 | * 94 | * The above copyright notice and this permission notice shall be included in all 95 | * copies or substantial portions of the Software. 96 | * 97 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 98 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 99 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 100 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 101 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 102 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 103 | * SOFTWARE. 104 | */ 105 | 106 | //! moment.js 107 | -------------------------------------------------------------------------------- /web_root/static/js/2.d8260731.chunk.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | object-assign 3 | (c) Sindre Sorhus 4 | @license MIT 5 | */ 6 | 7 | /*! 8 | Copyright (c) 2017 Jed Watson. 9 | Licensed under the MIT License (MIT), see 10 | http://jedwatson.github.io/classnames 11 | */ 12 | 13 | /*! 14 | * Chart.js v2.9.4 15 | * https://www.chartjs.org 16 | * (c) 2020 Chart.js Contributors 17 | * Released under the MIT License 18 | */ 19 | 20 | /*! 21 | * perfect-scrollbar v1.5.0 22 | * Copyright 2020 Hyunje Jun, MDBootstrap and Contributors 23 | * Licensed under MIT 24 | */ 25 | 26 | /** 27 | * A better abstraction over CSS. 28 | * 29 | * @copyright Oleg Isonen (Slobodskoi) / Isonen 2014-present 30 | * @website https://github.com/cssinjs/jss 31 | * @license MIT 32 | */ 33 | 34 | /** @license React v0.19.1 35 | * scheduler.production.min.js 36 | * 37 | * Copyright (c) Facebook, Inc. and its affiliates. 38 | * 39 | * This source code is licensed under the MIT license found in the 40 | * LICENSE file in the root directory of this source tree. 41 | */ 42 | 43 | /** @license React v16.10.2 44 | * react-is.production.min.js 45 | * 46 | * Copyright (c) Facebook, Inc. and its affiliates. 47 | * 48 | * This source code is licensed under the MIT license found in the 49 | * LICENSE file in the root directory of this source tree. 50 | */ 51 | 52 | /** @license React v16.14.0 53 | * react-dom.production.min.js 54 | * 55 | * Copyright (c) Facebook, Inc. and its affiliates. 56 | * 57 | * This source code is licensed under the MIT license found in the 58 | * LICENSE file in the root directory of this source tree. 59 | */ 60 | 61 | /** @license React v16.14.0 62 | * react.production.min.js 63 | * 64 | * Copyright (c) Facebook, Inc. and its affiliates. 65 | * 66 | * This source code is licensed under the MIT license found in the 67 | * LICENSE file in the root directory of this source tree. 68 | */ 69 | 70 | /**! 71 | * @fileOverview Kickass library to create and place poppers near their reference elements. 72 | * @version 1.16.1-lts 73 | * @license 74 | * Copyright (c) 2016 Federico Zivolo and contributors 75 | * 76 | * Permission is hereby granted, free of charge, to any person obtaining a copy 77 | * of this software and associated documentation files (the "Software"), to deal 78 | * in the Software without restriction, including without limitation the rights 79 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 80 | * copies of the Software, and to permit persons to whom the Software is 81 | * furnished to do so, subject to the following conditions: 82 | * 83 | * The above copyright notice and this permission notice shall be included in all 84 | * copies or substantial portions of the Software. 85 | * 86 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 87 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 88 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 89 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 90 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 91 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 92 | * SOFTWARE. 93 | */ 94 | 95 | //! moment.js 96 | -------------------------------------------------------------------------------- /web_root/static/js/main.2a00f055.chunk.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | 3 | ========================================================= 4 | * Material Dashboard React - v1.8.0 5 | ========================================================= 6 | 7 | * Product Page: https://www.creative-tim.com/product/material-dashboard-react 8 | * Copyright 2019 Creative Tim (https://www.creative-tim.com) 9 | * Licensed under MIT (https://github.com/creativetimofficial/material-dashboard-react/blob/master/LICENSE.md) 10 | 11 | * Coded by Creative Tim 12 | 13 | ========================================================= 14 | 15 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 16 | 17 | */ 18 | -------------------------------------------------------------------------------- /web_root/static/js/main.8156b384.chunk.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | 3 | ========================================================= 4 | * Material Dashboard React - v1.8.0 5 | ========================================================= 6 | 7 | * Product Page: https://www.creative-tim.com/product/material-dashboard-react 8 | * Copyright 2019 Creative Tim (https://www.creative-tim.com) 9 | * Licensed under MIT (https://github.com/creativetimofficial/material-dashboard-react/blob/master/LICENSE.md) 10 | 11 | * Coded by Creative Tim 12 | 13 | ========================================================= 14 | 15 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 16 | 17 | */ 18 | -------------------------------------------------------------------------------- /web_root/static/js/main.851f2e9f.chunk.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | 3 | ========================================================= 4 | * Material Dashboard React - v1.8.0 5 | ========================================================= 6 | 7 | * Product Page: https://www.creative-tim.com/product/material-dashboard-react 8 | * Copyright 2019 Creative Tim (https://www.creative-tim.com) 9 | * Licensed under MIT (https://github.com/creativetimofficial/material-dashboard-react/blob/master/LICENSE.md) 10 | 11 | * Coded by Creative Tim 12 | 13 | ========================================================= 14 | 15 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 16 | 17 | */ 18 | -------------------------------------------------------------------------------- /web_root/static/js/main.9fd6f9c0.chunk.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | 3 | ========================================================= 4 | * Material Dashboard React - v1.8.0 5 | ========================================================= 6 | 7 | * Product Page: https://www.creative-tim.com/product/material-dashboard-react 8 | * Copyright 2019 Creative Tim (https://www.creative-tim.com) 9 | * Licensed under MIT (https://github.com/creativetimofficial/material-dashboard-react/blob/master/LICENSE.md) 10 | 11 | * Coded by Creative Tim 12 | 13 | ========================================================= 14 | 15 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 16 | 17 | */ 18 | -------------------------------------------------------------------------------- /web_root/static/js/runtime-main.412f7afe.js: -------------------------------------------------------------------------------- 1 | !function(e){function r(r){for(var n,l,a=r[0],p=r[1],f=r[2],c=0,s=[];c 2 | 3 | -------------------------------------------------------------------------------- /web_root/static/media/nano_white.ad11d364.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /web_root/static/media/sidebar.18c01f03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/project-nano/frontend/18d3341c2dae56d42b1978ca87a0e6bcc4befbbe/web_root/static/media/sidebar.18c01f03.jpg --------------------------------------------------------------------------------