mirror of
https://github.com/donaldzou/WGDashboard.git
synced 2024-11-06 16:00:28 +01:00
20240323 Commit
Finished implementing peer settings dropdown and planned how peer settings will be link
This commit is contained in:
parent
0aa4c8af6f
commit
f1e71ecb78
1
src/static/app/.gitignore
vendored
1
src/static/app/.gitignore
vendored
@ -9,7 +9,6 @@ lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
BIN
src/static/app/dist/assets/bootstrap-icons.woff
vendored
Normal file
BIN
src/static/app/dist/assets/bootstrap-icons.woff
vendored
Normal file
Binary file not shown.
BIN
src/static/app/dist/assets/bootstrap-icons.woff2
vendored
Normal file
BIN
src/static/app/dist/assets/bootstrap-icons.woff2
vendored
Normal file
Binary file not shown.
9
src/static/app/dist/assets/index.css
vendored
Normal file
9
src/static/app/dist/assets/index.css
vendored
Normal file
File diff suppressed because one or more lines are too long
40
src/static/app/dist/assets/index.js
vendored
Normal file
40
src/static/app/dist/assets/index.js
vendored
Normal file
File diff suppressed because one or more lines are too long
938
src/static/app/dist/dashboard.css
vendored
Normal file
938
src/static/app/dist/dashboard.css
vendored
Normal file
@ -0,0 +1,938 @@
|
||||
body {
|
||||
font-size: .875rem;
|
||||
/*font-family: 'Poppins', sans-serif;*/
|
||||
}
|
||||
|
||||
.codeFont{
|
||||
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
}
|
||||
|
||||
.feather {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.dashboardLogo{
|
||||
background: -webkit-linear-gradient(#178bff, #ff4a00);
|
||||
}
|
||||
|
||||
/*
|
||||
* Sidebar
|
||||
*/
|
||||
|
||||
/*.sidebar {*/
|
||||
/* position: fixed;*/
|
||||
/* top: 0;*/
|
||||
/* bottom: 0;*/
|
||||
/* left: 0;*/
|
||||
/* z-index: 100;*/
|
||||
/* !* Behind the navbar *!*/
|
||||
/* padding: 48px 0 0;*/
|
||||
/* !* Height of navbar *!*/
|
||||
/* box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);*/
|
||||
/*}*/
|
||||
|
||||
/*.sidebar-sticky {*/
|
||||
/* position: relative;*/
|
||||
/* top: 0;*/
|
||||
/* height: calc(100vh - 48px);*/
|
||||
/* padding-top: .5rem;*/
|
||||
/* overflow-x: hidden;*/
|
||||
/* overflow-y: auto;*/
|
||||
/* !* Scrollable contents if viewport is shorter than content. *!*/
|
||||
/*}*/
|
||||
|
||||
/*@supports ((position: -webkit-sticky) or (position: sticky)) {*/
|
||||
/* .sidebar-sticky {*/
|
||||
/* position: -webkit-sticky;*/
|
||||
/* position: sticky;*/
|
||||
/* }*/
|
||||
/*}*/
|
||||
|
||||
.sidebar .nav-link, .bottomNavContainer .nav-link{
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
transition: 0.2s cubic-bezier(0.82, -0.07, 0, 1.01);
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
padding-left: 30px;
|
||||
background-color: #dfdfdf;
|
||||
}
|
||||
|
||||
.sidebar .nav-link .feather {
|
||||
margin-right: 4px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.sidebar .nav-link.active, .bottomNavContainer .nav-link.active {
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
.sidebar .nav-link:hover .feather,
|
||||
.sidebar .nav-link.active .feather {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.sidebar-heading {
|
||||
font-size: .75rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Navbar
|
||||
*/
|
||||
|
||||
.navbar-brand {
|
||||
padding-top: .75rem;
|
||||
padding-bottom: .75rem;
|
||||
font-size: 1rem;
|
||||
/*background-color: rgba(0, 0, 0, .25);*/
|
||||
/*box-shadow: inset -1px 0 0 rgba(0, 0, 0, .25);*/
|
||||
}
|
||||
|
||||
.navbar .navbar-toggler {
|
||||
top: .25rem;
|
||||
right: 1rem;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.form-control:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.navbar .form-control {
|
||||
padding: .75rem 1rem;
|
||||
border-width: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.form-control-dark {
|
||||
color: #fff;
|
||||
background-color: rgba(255, 255, 255, .1);
|
||||
border-color: rgba(255, 255, 255, .1);
|
||||
}
|
||||
|
||||
.form-control-dark:focus {
|
||||
border-color: transparent;
|
||||
box-shadow: 0 0 0 3px rgba(255, 255, 255, .25);
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50px;
|
||||
display: inline-block;
|
||||
margin-left: auto !important;
|
||||
}
|
||||
|
||||
.dot-running {
|
||||
background-color: #28a745!important;
|
||||
box-shadow: 0 0 0 0.2rem #28a74545;
|
||||
}
|
||||
|
||||
.h6-dot-running {
|
||||
margin-left: 0.3rem;
|
||||
}
|
||||
|
||||
.dot-stopped {
|
||||
background-color: #6c757d!important;
|
||||
}
|
||||
|
||||
.card-running {
|
||||
border-color: #28a745;
|
||||
}
|
||||
|
||||
.info h6 {
|
||||
line-break: anywhere;
|
||||
transition: all 0.4s cubic-bezier(0.96, -0.07, 0.34, 1.01);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.info .row .col-sm {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.info .row .col-sm small {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.info .row .col-sm small strong:last-child(1) {
|
||||
margin-left: auto !important;
|
||||
}
|
||||
|
||||
.btn-control {
|
||||
border: none !important;
|
||||
padding: 0;
|
||||
margin: 0 1rem 0 0;
|
||||
}
|
||||
|
||||
.btn-control:hover{
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.btn-control:active,
|
||||
.btn-control:focus {
|
||||
background-color: transparent !important;
|
||||
border: none !important;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.btn-qrcode-peer {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.btn-qrcode-peer:active,
|
||||
.btn-qrcode-peer:hover {
|
||||
transform: scale(0.9) rotate(180deg);
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
.btn-download-peer:active,
|
||||
.btn-download-peer:hover {
|
||||
color: #17a2b8 !important;
|
||||
transform: translateY(5px);
|
||||
}
|
||||
|
||||
.share_peer_btn_group .btn-control {
|
||||
margin: 0 0 0 1rem;
|
||||
padding: 0 !important;
|
||||
transition: all 0.4s cubic-bezier(1, -0.43, 0, 1.37);
|
||||
}
|
||||
|
||||
.btn-control:hover {
|
||||
background: white;
|
||||
}
|
||||
|
||||
.btn-delete-peer:hover {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.btn-lock-peer:hover {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.btn-lock-peer.lock{
|
||||
color: #6c757d
|
||||
}
|
||||
|
||||
.btn-lock-peer.lock:hover{
|
||||
color: #6c757d
|
||||
}
|
||||
|
||||
.btn-control.btn-outline-primary:hover{
|
||||
color: #007bff
|
||||
}
|
||||
|
||||
/* .btn-setting-peer:hover {
|
||||
color: #007bff
|
||||
} */
|
||||
|
||||
.btn-download-peer:hover {
|
||||
color: #17a2b8;
|
||||
}
|
||||
|
||||
.login-container {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.card-col {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.switch {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.switch:hover {
|
||||
text-decoration: none
|
||||
}
|
||||
|
||||
.btn-group-label:hover {
|
||||
color: #007bff;
|
||||
border-color: #007bff;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.peer_data_group {
|
||||
text-align: right;
|
||||
display: flex;
|
||||
margin-bottom: 0.5rem
|
||||
}
|
||||
|
||||
.peer_data_group p {
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 0;
|
||||
margin-right: 1rem
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.peer_data_group {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.index-switch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
main {
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.peer_list {
|
||||
margin-bottom: 7rem
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.add_btn {
|
||||
bottom: 1.5rem !important;
|
||||
}
|
||||
.peer_list {
|
||||
margin-bottom: 7rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-manage-group {
|
||||
z-index: 99;
|
||||
position: fixed;
|
||||
bottom: 3rem;
|
||||
right: 2rem;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.btn-manage-group .setting_btn_menu {
|
||||
position: absolute;
|
||||
top: -124px;
|
||||
background-color: white;
|
||||
padding: 1rem 0;
|
||||
right: 0;
|
||||
box-shadow: 0 10px 20px rgb(0 0 0 / 19%), 0 6px 6px rgb(0 0 0 / 23%);
|
||||
border-radius: 10px;
|
||||
min-width: 250px;
|
||||
display: none;
|
||||
transform: translateY(-30px);
|
||||
opacity: 0;
|
||||
transition: all 0.3s cubic-bezier(0.58, 0.03, 0.05, 1.28);
|
||||
}
|
||||
|
||||
.btn-manage-group .setting_btn_menu.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.setting_btn_menu.showing {
|
||||
transform: translateY(0px);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.setting_btn_menu a {
|
||||
display: flex;
|
||||
padding: 0.5rem 1rem;
|
||||
transition: all 0.1s ease-in-out;
|
||||
font-size: 1rem;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.setting_btn_menu a:hover {
|
||||
background-color: #efefef;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.setting_btn_menu a i {
|
||||
margin-right: auto !important;
|
||||
}
|
||||
|
||||
.add_btn {
|
||||
height: 54px;
|
||||
z-index: 99;
|
||||
border-radius: 100px !important;
|
||||
padding: 0 14px;
|
||||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
|
||||
margin-right: 1rem;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.setting_btn {
|
||||
height: 54px;
|
||||
z-index: 99;
|
||||
border-radius: 100px !important;
|
||||
padding: 0 14px;
|
||||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
@-webkit-keyframes rotating
|
||||
/* Safari and Chrome */
|
||||
|
||||
{
|
||||
from {
|
||||
-webkit-transform: rotate(0deg);
|
||||
-o-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
-webkit-transform: rotate(360deg);
|
||||
-o-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rotating {
|
||||
from {
|
||||
-ms-transform: rotate(0deg);
|
||||
-moz-transform: rotate(0deg);
|
||||
-webkit-transform: rotate(0deg);
|
||||
-o-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
-ms-transform: rotate(360deg);
|
||||
-moz-transform: rotate(360deg);
|
||||
-webkit-transform: rotate(360deg);
|
||||
-o-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.rotating::before {
|
||||
-webkit-animation: rotating 0.75s linear infinite;
|
||||
-moz-animation: rotating 0.75s linear infinite;
|
||||
-ms-animation: rotating 0.75s linear infinite;
|
||||
-o-animation: rotating 0.75s linear infinite;
|
||||
animation: rotating 0.75s linear infinite;
|
||||
}
|
||||
|
||||
.peer_private_key_textbox_switch {
|
||||
position: absolute;
|
||||
right: 2rem;
|
||||
transform: translateY(-28px);
|
||||
font-size: 1.2rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#peer_private_key_textbox,
|
||||
#private_key,
|
||||
#public_key,
|
||||
#peer_preshared_key_textbox {
|
||||
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
transition: 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.key {
|
||||
transition: 0.2s ease-in-out;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.key:hover {
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
.card {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.peer_list .card .button-group {
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
border-radius: 8px;
|
||||
/*padding: 0.6rem 0.9em;*/
|
||||
}
|
||||
|
||||
.login-box #username,
|
||||
.login-box #password {
|
||||
padding: 0.6rem calc( 0.9rem + 32px);
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
.login-box label[for="username"],
|
||||
.login-box label[for="password"] {
|
||||
font-size: 1rem;
|
||||
margin: 0 !important;
|
||||
transform: translateY(2.1rem) translateX(1rem);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
/*label[for="password"]{*/
|
||||
|
||||
|
||||
/* transform: translateY(32px) translateX(16px);*/
|
||||
|
||||
|
||||
/*}*/
|
||||
|
||||
.modal-content {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.tooltip-inner {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
@-webkit-keyframes loading {
|
||||
0% {
|
||||
background-color: #dfdfdf;
|
||||
}
|
||||
50% {
|
||||
background-color: #adadad;
|
||||
}
|
||||
100% {
|
||||
background-color: #dfdfdf;
|
||||
}
|
||||
}
|
||||
|
||||
@-moz-keyframes loading {
|
||||
0% {
|
||||
background-color: #dfdfdf;
|
||||
}
|
||||
50% {
|
||||
background-color: #adadad;
|
||||
}
|
||||
100% {
|
||||
background-color: #dfdfdf;
|
||||
}
|
||||
}
|
||||
|
||||
.conf_card {
|
||||
transition: 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.conf_card:hover {
|
||||
border-color: #007bff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.info_loading {
|
||||
/* animation: loading 2s infinite ease-in-out;
|
||||
/* border-radius: 5px; */
|
||||
height: 19.19px;
|
||||
/* transition: 0.3s ease-in-out; */
|
||||
|
||||
/* transform: translateX(40px); */
|
||||
opacity: 0 !important;
|
||||
}
|
||||
|
||||
#conf_status_btn {
|
||||
transition: 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
#conf_status_btn.info_loading {
|
||||
height: 38px;
|
||||
border-radius: 5px;
|
||||
animation: loading 3s infinite ease-in-out;
|
||||
}
|
||||
|
||||
#qrcode_img img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#selected_ip_list .badge,
|
||||
#selected_peer_list .badge {
|
||||
margin: 0.1rem
|
||||
}
|
||||
|
||||
#add_modal.ip_modal_open {
|
||||
transition: filter 0.2s ease-in-out;
|
||||
filter: brightness(0.5);
|
||||
}
|
||||
|
||||
#delete_bulk_modal .list-group a.active {
|
||||
background-color: #dc3545;
|
||||
border-color: #dc3545;
|
||||
}
|
||||
|
||||
#selected_peer_list {
|
||||
max-height: 80px;
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.no-response {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
background: #000000ba;
|
||||
z-index: 10000;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
transition: all 1s ease-in-out;
|
||||
}
|
||||
|
||||
.no-response.active {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.no-response.active.show {
|
||||
opacity: 100;
|
||||
}
|
||||
|
||||
.no-response .container>* {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.no-responding {
|
||||
transition: all 1s ease-in-out;
|
||||
filter: blur(10px);
|
||||
}
|
||||
|
||||
pre.index-alert {
|
||||
margin-bottom: 0;
|
||||
padding: 1rem;
|
||||
background-color: #343a40;
|
||||
border: 1px solid rgba(0, 0, 0, .125);
|
||||
border-radius: .25rem;
|
||||
margin-top: 1rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.peerNameCol {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 0.2rem
|
||||
}
|
||||
|
||||
.peerName {
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.peerLightContainer {
|
||||
text-transform: uppercase;
|
||||
margin: 0;
|
||||
margin-left: auto !important;
|
||||
}
|
||||
|
||||
.conf_card .dot,
|
||||
.info .dot {
|
||||
transform: translateX(10px);
|
||||
}
|
||||
|
||||
#config_body {
|
||||
transition: 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
|
||||
#config_body.firstLoading {
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.chartTitle {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.chartControl {
|
||||
margin-bottom: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.chartTitle h6 {
|
||||
margin-bottom: 0;
|
||||
line-height: 1;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.chartContainer.fullScreen {
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
background-color: white;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: calc( 100% + 15px);
|
||||
height: 100%;
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
.chartContainer.fullScreen .col-sm {
|
||||
padding-right: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.chartContainer.fullScreen .chartCanvasContainer {
|
||||
width: 100%;
|
||||
height: calc( 100% - 47px) !important;
|
||||
max-height: calc( 100% - 47px) !important;
|
||||
}
|
||||
|
||||
#switch{
|
||||
transition: all 200ms ease-in;
|
||||
}
|
||||
|
||||
.toggle--switch{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.toggleLabel{
|
||||
width: 64px;
|
||||
height: 32px;
|
||||
background-color: #6c757d17;
|
||||
display: flex;
|
||||
position: relative;
|
||||
border: 2px solid #6c757d8c;
|
||||
border-radius: 100px;
|
||||
transition: all 200ms ease-in;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.toggle--switch.waiting + .toggleLabel{
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.toggleLabel::before{
|
||||
background-color: #6c757d;
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
content: "";
|
||||
border-radius: 100px;
|
||||
margin: 1px;
|
||||
position: absolute;
|
||||
animation-name: off;
|
||||
animation-duration: 350ms;
|
||||
animation-fill-mode: forwards;
|
||||
transition: all 200ms ease-in;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.toggleLabel:hover::before{
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
|
||||
|
||||
.toggle--switch:checked + .toggleLabel{
|
||||
background-color: #007bff17 !important;
|
||||
border: 2px solid #007bff8c;
|
||||
}
|
||||
|
||||
.toggle--switch:checked + .toggleLabel::before{
|
||||
background-color: #007bff;
|
||||
animation-name: on;
|
||||
animation-duration: 350ms;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
@keyframes on {
|
||||
0%{
|
||||
left: 0px;
|
||||
}
|
||||
60%{
|
||||
left: 0px;
|
||||
width: 40px;
|
||||
}
|
||||
100%{
|
||||
left: 32px;
|
||||
width: 26px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes off {
|
||||
0%{
|
||||
left: 32px;
|
||||
}
|
||||
60%{
|
||||
left: 18px;
|
||||
width: 40px;
|
||||
}
|
||||
100%{
|
||||
left: 0px;
|
||||
width: 26px;
|
||||
}
|
||||
}
|
||||
|
||||
.toastContainer{
|
||||
z-index: 99999 !important;
|
||||
}
|
||||
|
||||
.toast{
|
||||
min-width: 300px;
|
||||
background-color: rgba(255,255,255,1);
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
.toast-header{
|
||||
background-color: rgba(255,255,255);
|
||||
}
|
||||
|
||||
.toast-progressbar{
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background-color: #007bff;
|
||||
border-bottom-left-radius: .25rem;
|
||||
}
|
||||
|
||||
.addConfigurationAvailableIPs{
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.input-feedback{
|
||||
display: none;
|
||||
}
|
||||
|
||||
#addConfigurationModal label{
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#addConfigurationModal label a{
|
||||
margin-left: auto !important;
|
||||
}
|
||||
|
||||
#reGeneratePrivateKey{
|
||||
border-top-right-radius: 10px;
|
||||
border-bottom-right-radius: 10px;
|
||||
}
|
||||
|
||||
.addConfigurationToggleStatus.waiting{
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/*.conf_card .card-body .row .card-col{*/
|
||||
/* margin-bottom: 0.5rem;*/
|
||||
/*}*/
|
||||
|
||||
.peerDataUsageChartContainer{
|
||||
min-height: 50vh;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.peerDataUsageChartControl{
|
||||
display: block !important;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.peerDataUsageChartControl .switchUnit{
|
||||
width: 33.3%;
|
||||
}
|
||||
|
||||
.peerDataUsageChartControl .switchTimePeriod{
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
@media (min-width: 1200px){
|
||||
#peerDataUsage .modal-xl {
|
||||
max-width: 95vw;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom{
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
@media (max-width: 768px){
|
||||
.bottom{
|
||||
display: block;
|
||||
}
|
||||
|
||||
.btn-manage-group{
|
||||
bottom: calc( 3rem + 40px + env(safe-area-inset-bottom, 5px));
|
||||
}
|
||||
|
||||
main{
|
||||
padding-bottom: calc( 3rem + 40px + env(safe-area-inset-bottom, 5px));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.bottomNavContainer{
|
||||
display: flex;
|
||||
color: #333;
|
||||
padding-bottom: env(safe-area-inset-bottom, 5px);
|
||||
box-shadow: inset 0 1px 0 rgb(0 0 0 / 10%);
|
||||
}
|
||||
|
||||
.bottomNavButton{
|
||||
width: 25vw;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin: 0.7rem 0;
|
||||
color: rgba(51, 51, 51, 0.5);
|
||||
cursor: pointer;
|
||||
transition: all ease-in 0.2s;
|
||||
}
|
||||
|
||||
.bottomNavButton.active{
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.bottomNavButton i{
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.bottomNavButton .subNav{
|
||||
width: 100vw;
|
||||
position: absolute;
|
||||
z-index: 10000;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: #272b30;
|
||||
display: none;
|
||||
animation-duration: 400ms;
|
||||
padding-bottom: env(safe-area-inset-bottom, 5px);
|
||||
}
|
||||
|
||||
.bottomNavButton .subNav.active{
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
.bottomNavButton .subNav .nav .nav-item .nav-link{
|
||||
padding: 0.7rem 1rem;
|
||||
}
|
||||
|
||||
.bottomNavWrapper{
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: #000000a1;
|
||||
position: fixed;
|
||||
z-index: 1030;
|
||||
display: none;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.bottomNavWrapper.active{
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sb-update-url .dot-running{
|
||||
transform: translateX(10px);
|
||||
}
|
||||
|
||||
.list-group-item{
|
||||
transition: all 0.1s ease-in;
|
||||
}
|
||||
|
||||
.theme-switch-btn{
|
||||
width: 100%;
|
||||
}
|
BIN
src/static/app/dist/favicon.ico
vendored
Normal file
BIN
src/static/app/dist/favicon.ico
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
14
src/static/app/dist/index.html
vendored
Normal file
14
src/static/app/dist/index.html
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/static/app/dist/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vite App</title>
|
||||
<script type="module" crossorigin src="/static/app/dist/assets/index.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/static/app/dist/assets/index.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" class="w-100 vh-100"></div>
|
||||
</body>
|
||||
</html>
|
@ -1,8 +1,10 @@
|
||||
<script>
|
||||
import { ref } from 'vue'
|
||||
import { onClickOutside } from '@vueuse/core'
|
||||
import PeerSettingsDropdown from "@/components/configurationComponents/peerSettingsDropdown.vue";
|
||||
export default {
|
||||
name: "peer",
|
||||
components: {PeerSettingsDropdown},
|
||||
props: {
|
||||
Peer: Object
|
||||
},
|
||||
@ -19,8 +21,13 @@ export default {
|
||||
});
|
||||
return {target, subMenuOpened}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
computed: {
|
||||
getLatestHandshake(){
|
||||
if (this.Peer.latest_handshake.includes(",")){
|
||||
return this.Peer.latest_handshake.split(",")[0]
|
||||
}
|
||||
return this.Peer.latest_handshake;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -40,7 +47,7 @@ export default {
|
||||
</span>
|
||||
<span class="text-secondary" v-if="Peer.latest_handshake !== 'No Handshake'">
|
||||
<i class="bi bi-arrows-angle-contract"></i>
|
||||
{{Peer.latest_handshake}} ago
|
||||
{{getLatestHandshake}} ago
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -57,17 +64,20 @@ export default {
|
||||
<small class="text-muted">Allowed IP</small>
|
||||
<p class="mb-0"><samp>{{Peer.allowed_ip}}</samp></p>
|
||||
</div>
|
||||
<div class="ms-auto px-2">
|
||||
<a role="button" class="text-body" @click="this.subMenuOpened = true">
|
||||
<div class="ms-auto px-2 rounded-3 subMenuBtn"
|
||||
:class="{active: this.subMenuOpened}"
|
||||
>
|
||||
<a role="button" class="text-body"
|
||||
|
||||
@click="this.subMenuOpened = true">
|
||||
<h5 class="mb-0"><i class="bi bi-three-dots"></i></h5>
|
||||
</a>
|
||||
<Transition name="slide-fade">
|
||||
<ul class="dropdown-menu mt-2 shadow-lg dropdown-menu-left d-block"
|
||||
v-if="this.subMenuOpened"
|
||||
ref="target">
|
||||
<li>
|
||||
<a class="dropdown-item d-flex" role="button" ></a></li>
|
||||
</ul>
|
||||
<PeerSettingsDropdown
|
||||
:Peer="Peer"
|
||||
v-if="this.subMenuOpened"
|
||||
ref="target"
|
||||
></PeerSettingsDropdown>
|
||||
</Transition>
|
||||
</div>
|
||||
</div>
|
||||
@ -86,4 +96,8 @@ export default {
|
||||
transform: translateY(20px);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.subMenuBtn.active{
|
||||
background-color: #ffffff20;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,489 @@
|
||||
<script>
|
||||
import PeerSearch from "@/components/configurationComponents/peerSearch.vue";
|
||||
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
||||
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
|
||||
import {fetchGet} from "@/utilities/fetch.js";
|
||||
import Peer from "@/components/configurationComponents/peer.vue";
|
||||
import { Line, Bar } from 'vue-chartjs'
|
||||
import Fuse from "fuse.js";
|
||||
import {
|
||||
Chart,
|
||||
ArcElement,
|
||||
LineElement,
|
||||
BarElement,
|
||||
PointElement,
|
||||
BarController,
|
||||
BubbleController,
|
||||
DoughnutController,
|
||||
LineController,
|
||||
PieController,
|
||||
PolarAreaController,
|
||||
RadarController,
|
||||
ScatterController,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
LogarithmicScale,
|
||||
RadialLinearScale,
|
||||
TimeScale,
|
||||
TimeSeriesScale,
|
||||
Decimation,
|
||||
Filler,
|
||||
Legend,
|
||||
Title,
|
||||
Tooltip
|
||||
} from 'chart.js';
|
||||
import dayjs from "dayjs";
|
||||
|
||||
Chart.register(
|
||||
ArcElement,
|
||||
LineElement,
|
||||
BarElement,
|
||||
PointElement,
|
||||
BarController,
|
||||
BubbleController,
|
||||
DoughnutController,
|
||||
LineController,
|
||||
PieController,
|
||||
PolarAreaController,
|
||||
RadarController,
|
||||
ScatterController,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
LogarithmicScale,
|
||||
RadialLinearScale,
|
||||
TimeScale,
|
||||
TimeSeriesScale,
|
||||
Decimation,
|
||||
Filler,
|
||||
Legend,
|
||||
Title,
|
||||
Tooltip
|
||||
);
|
||||
|
||||
export default {
|
||||
name: "peerList",
|
||||
components: {PeerSearch, Peer, Line, Bar},
|
||||
setup(){
|
||||
const dashboardConfigurationStore = DashboardConfigurationStore();
|
||||
const wireguardConfigurationStore = WireguardConfigurationsStore();
|
||||
return {dashboardConfigurationStore, wireguardConfigurationStore}
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
loading: false,
|
||||
error: null,
|
||||
configurationInfo: [],
|
||||
configurationPeers: [],
|
||||
historyDataSentDifference: [],
|
||||
historyDataReceivedDifference: [],
|
||||
historySentData: {
|
||||
labels: [],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Data Sent',
|
||||
data: [],
|
||||
fill: false,
|
||||
borderColor: '#198754',
|
||||
tension: 0
|
||||
},
|
||||
],
|
||||
},
|
||||
historyReceiveData: {
|
||||
labels: [],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Data Received',
|
||||
data: [],
|
||||
fill: false,
|
||||
borderColor: '#0d6efd',
|
||||
tension: 0
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'$route.params.id': {
|
||||
immediate: true,
|
||||
handler(){
|
||||
clearInterval(this.interval)
|
||||
this.loading = true;
|
||||
let id = this.$route.params.id;
|
||||
this.configurationInfo = [];
|
||||
this.configurationPeers = [];
|
||||
if (id){
|
||||
this.getPeers(id)
|
||||
this.setInterval();
|
||||
}
|
||||
}
|
||||
},
|
||||
'dashboardConfigurationStore.Configuration.Server.dashboard_refresh_interval'(){
|
||||
clearInterval(this.interval);
|
||||
this.setInterval();
|
||||
}
|
||||
},
|
||||
beforeRouteLeave(){
|
||||
clearInterval(this.interval)
|
||||
},
|
||||
methods:{
|
||||
getPeers(id){
|
||||
fetchGet("/api/getWireguardConfigurationInfo",
|
||||
{
|
||||
configurationName: id
|
||||
}, (res) => {
|
||||
this.configurationInfo = res.data.configurationInfo;
|
||||
this.configurationPeers = res.data.configurationPeers;
|
||||
this.loading = false;
|
||||
if (this.configurationPeers.length > 0){
|
||||
const sent = this.configurationPeers.map(x => x.total_sent + x.cumu_sent).reduce((x,y) => x + y).toFixed(4);
|
||||
const receive = this.configurationPeers.map(x => x.total_receive + x.cumu_receive).reduce((x,y) => x + y).toFixed(4);
|
||||
if (
|
||||
this.historyDataSentDifference[this.historyDataSentDifference.length - 1] !== sent
|
||||
){
|
||||
if (this.historyDataSentDifference.length > 0){
|
||||
this.historySentData = {
|
||||
labels: [...this.historySentData.labels, dayjs().format("HH:mm:ss A")],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Data Sent',
|
||||
data: [...this.historySentData.datasets[0].data,
|
||||
((sent - this.historyDataSentDifference[this.historyDataSentDifference.length - 1])*1000).toFixed(4)],
|
||||
fill: false,
|
||||
borderColor: ' #198754',
|
||||
tension: 0
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
this.historyDataSentDifference.push(sent)
|
||||
}
|
||||
if (
|
||||
this.historyDataReceivedDifference[this.historyDataReceivedDifference.length - 1] !== receive
|
||||
){
|
||||
if (this.historyDataReceivedDifference.length > 0){
|
||||
this.historyReceiveData = {
|
||||
labels: [...this.historyReceiveData.labels, dayjs().format("HH:mm:ss A")],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Data Received',
|
||||
data: [...this.historyReceiveData.datasets[0].data,
|
||||
((receive - this.historyDataReceivedDifference[this.historyDataReceivedDifference.length - 1])*1000).toFixed(4)],
|
||||
fill: false,
|
||||
borderColor: '#0d6efd',
|
||||
tension: 0
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
this.historyDataReceivedDifference.push(receive)
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
setInterval(){
|
||||
this.interval = setInterval(() => {
|
||||
this.getPeers(this.$route.params.id)
|
||||
}, parseInt(this.dashboardConfigurationStore.Configuration.Server.dashboard_refresh_interval))
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
configurationSummary(){
|
||||
return {
|
||||
connectedPeers: this.configurationPeers.filter(x => x.status === "running").length,
|
||||
totalUsage: this.configurationPeers.length > 0 ? this.configurationPeers.map(x => x.total_data + x.cumu_data).reduce((a, b) => a + b) : 0,
|
||||
totalReceive: this.configurationPeers.length > 0 ? this.configurationPeers.map(x => x.total_receive + x.cumu_receive).reduce((a, b) => a + b) : 0,
|
||||
totalSent: this.configurationPeers.length > 0 ? this.configurationPeers.map(x => x.total_sent + x.cumu_sent).reduce((a, b) => a + b) : 0
|
||||
}
|
||||
},
|
||||
receiveData(){
|
||||
return this.historyReceiveData
|
||||
},
|
||||
sentData(){
|
||||
return this.historySentData
|
||||
},
|
||||
individualDataUsage(){
|
||||
return {
|
||||
labels: this.configurationPeers.map(x => {
|
||||
if (x.name) return x.name
|
||||
return `Untitled Peer - ${x.id}`
|
||||
}),
|
||||
datasets: [{
|
||||
label: 'Total Data Usage',
|
||||
data: this.configurationPeers.map(x => x.cumu_data + x.total_data),
|
||||
backgroundColor: this.configurationPeers.map(x => `#0dcaf0`),
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: (tooltipItem) => {
|
||||
return `${tooltipItem.formattedValue} GB`
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
},
|
||||
individualDataUsageChartOption(){
|
||||
return {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
ticks: {
|
||||
display: false,
|
||||
},
|
||||
grid: {
|
||||
display: false
|
||||
},
|
||||
},
|
||||
y:{
|
||||
ticks: {
|
||||
callback: (val, index) => {
|
||||
return `${val} GB`
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
display: false
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
chartOptions() {
|
||||
return {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: (tooltipItem) => {
|
||||
return `${tooltipItem.formattedValue} MB/s`
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
ticks: {
|
||||
display: false,
|
||||
},
|
||||
grid: {
|
||||
display: false
|
||||
},
|
||||
},
|
||||
y:{
|
||||
ticks: {
|
||||
callback: (val, index) => {
|
||||
return `${val} MB/s`
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
display: false
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
searchPeers(){
|
||||
const fuse = new Fuse(this.configurationPeers, {
|
||||
keys: ["name", "id", "allowed_ip"]
|
||||
});
|
||||
|
||||
const result = this.wireguardConfigurationStore.searchString ? fuse.search(this.wireguardConfigurationStore.searchString).map(x => x.item) : this.configurationPeers;
|
||||
|
||||
return result.slice().sort((a, b) => {
|
||||
if ( a[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort]
|
||||
< b[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort] ){
|
||||
return -1;
|
||||
}
|
||||
if ( a[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort]
|
||||
> b[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort]){
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="!this.loading">
|
||||
<div>
|
||||
<small CLASS="text-muted">CONFIGURATION</small>
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<h1 class="mb-0"><samp>{{this.configurationInfo.Name}}</samp></h1>
|
||||
<div class="dot active ms-0"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3 gy-2 gx-2 mb-2">
|
||||
<div class="col-6 col-lg-3">
|
||||
<div class="card rounded-3 bg-transparent shadow-sm">
|
||||
<div class="card-body py-2">
|
||||
<p class="mb-0 text-muted"><small>Address</small></p>
|
||||
{{this.configurationInfo.Address}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-lg-3">
|
||||
<div class="card rounded-3 bg-transparent shadow-sm">
|
||||
<div class="card-body py-2">
|
||||
<p class="mb-0 text-muted"><small>Listen Port</small></p>
|
||||
{{this.configurationInfo.ListenPort}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="word-break: break-all" class="col-12 col-lg-6">
|
||||
<div class="card rounded-3 bg-transparent shadow-sm">
|
||||
<div class="card-body py-2">
|
||||
<p class="mb-0 text-muted"><small>Public Key</small></p>
|
||||
<samp>{{this.configurationInfo.PublicKey}}</samp>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row gx-2 gy-2 mb-2">
|
||||
<div class="col-6 col-lg-3">
|
||||
<div class="card rounded-3 bg-transparent shadow-sm">
|
||||
<div class="card-body d-flex">
|
||||
<div>
|
||||
<p class="mb-0 text-muted"><small>Connected Peers</small></p>
|
||||
<strong class="h4">{{configurationSummary.connectedPeers}}</strong>
|
||||
</div>
|
||||
<i class="bi bi-ethernet ms-auto h2 text-muted"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-lg-3">
|
||||
<div class="card rounded-3 bg-transparent shadow-sm">
|
||||
<div class="card-body d-flex">
|
||||
<div>
|
||||
<p class="mb-0 text-muted"><small>Total Usage</small></p>
|
||||
<strong class="h4">{{configurationSummary.totalUsage.toFixed(4)}} GB</strong>
|
||||
</div>
|
||||
<i class="bi bi-arrow-down-up ms-auto h2 text-muted"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-lg-3">
|
||||
<div class="card rounded-3 bg-transparent shadow-sm">
|
||||
<div class="card-body d-flex">
|
||||
<div>
|
||||
<p class="mb-0 text-muted"><small>Total Received</small></p>
|
||||
<strong class="h4 text-primary">{{configurationSummary.totalReceive.toFixed(4)}} GB</strong>
|
||||
</div>
|
||||
<i class="bi bi-arrow-down ms-auto h2 text-muted"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-lg-3">
|
||||
<div class="card rounded-3 bg-transparent shadow-sm">
|
||||
<div class="card-body d-flex">
|
||||
<div>
|
||||
<p class="mb-0 text-muted"><small>Total Sent</small></p>
|
||||
<strong class="h4 text-success">{{configurationSummary.totalSent.toFixed(4)}} GB</strong>
|
||||
</div>
|
||||
<i class="bi bi-arrow-up ms-auto h2 text-muted"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row gx-2 gy-2 mb-5">
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="card rounded-3 bg-transparent shadow-sm">
|
||||
<div class="card-header bg-transparent border-0"><small class="text-muted">Peers Total Data Usage</small></div>
|
||||
<div class="card-body pt-1">
|
||||
<Bar
|
||||
:data="individualDataUsage"
|
||||
:options="individualDataUsageChartOption"
|
||||
style="height: 200px; width: 100%"></Bar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm col-lg-3">
|
||||
<div class="card rounded-3 bg-transparent shadow-sm">
|
||||
<div class="card-header bg-transparent border-0"><small class="text-muted">Real Time Received Data Usage</small></div>
|
||||
<div class="card-body pt-1">
|
||||
<Line
|
||||
:options="chartOptions"
|
||||
:data="receiveData"
|
||||
style="width: 100%; height: 200px"
|
||||
></Line>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm col-lg-3">
|
||||
<div class="card rounded-3 bg-transparent shadow-sm">
|
||||
<div class="card-header bg-transparent border-0"><small class="text-muted">Real Time Sent Data Usage</small></div>
|
||||
<div class="card-body pt-1">
|
||||
<Line
|
||||
:options="chartOptions"
|
||||
:data="sentData"
|
||||
style="width: 100%; height: 200px"
|
||||
></Line>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<RouterView v-slot="{Component}">
|
||||
<Transition name="fade3" mode="out-in">
|
||||
<Component :is="Component"></Component>
|
||||
</Transition>
|
||||
</RouterView>
|
||||
<div class="d-flex align-items-center gap-3 mb-2 ">
|
||||
<h3>Peers</h3>
|
||||
<RouterLink
|
||||
to="./peer_settings"
|
||||
class="ms-auto text-secondary text-decoration-none"><i class="bi bi-sliders2 me-2"></i>Peer Settings</RouterLink>
|
||||
<a href="#" class="text-decoration-none"><i class="bi bi-plus-circle-fill me-2"></i>Add Peer</a>
|
||||
</div>
|
||||
<PeerSearch></PeerSearch>
|
||||
|
||||
<TransitionGroup name="list" tag="div" class="row gx-2 gy-2 z-0">
|
||||
<div class="col-12 col-lg-6 col-xl-4"
|
||||
:key="peer.id"
|
||||
v-for="peer in this.searchPeers">
|
||||
<Peer :Peer="peer"></Peer>
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.peerNav .nav-link{
|
||||
&.active{
|
||||
//background: linear-gradient(var(--degree), var(--brandColor1) var(--distance2), var(--brandColor2) 100%);
|
||||
//color: white;
|
||||
background-color: #efefef;
|
||||
}
|
||||
}
|
||||
|
||||
.list-move, /* apply transition to moving elements */
|
||||
.list-enter-active,
|
||||
.list-leave-active {
|
||||
transition: all 0.4s cubic-bezier(0.82, 0.58, 0.17, 0.9);
|
||||
}
|
||||
|
||||
.list-leave-active{
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.list-enter-from,
|
||||
.list-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
|
||||
/* ensure leaving items are taken out of layout flow so that moving
|
||||
animations can be calculated correctly. */
|
||||
.list-leave-active {
|
||||
position: absolute;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,32 @@
|
||||
<script>
|
||||
export default {
|
||||
name: "peerSettings"
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="peerSettingContainer w-100 h-100 position-absolute top-0 start-0">
|
||||
<div class="container d-flex h-100 w-100">
|
||||
<div class="card m-auto rounded-3 w-100">
|
||||
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4">
|
||||
<h4 class="mb-0">Peer Settings</h4>
|
||||
<router-link to="./" class="ms-auto btn">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<div class="card-body p-4">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.peerSettingContainer{
|
||||
background-color: #00000060;
|
||||
z-index: 1000;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,63 @@
|
||||
<script>
|
||||
export default {
|
||||
name: "peerSettingsDropdown",
|
||||
props: {
|
||||
Peer: Object
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul class="dropdown-menu mt-2 shadow-lg d-block rounded-3" style="max-width: 200px">
|
||||
<template v-if="!this.Peer.private_key">
|
||||
<li>
|
||||
<small class="w-100 dropdown-item text-muted"
|
||||
style="white-space: break-spaces; font-size: 0.7rem"
|
||||
>Download & QR Code is not available due to no <code>private key</code>
|
||||
set for this peer
|
||||
</small>
|
||||
</li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
</template>
|
||||
|
||||
<li>
|
||||
<a class="dropdown-item d-flex" role="button">
|
||||
<i class="me-auto bi bi-pen"></i> Edit
|
||||
</a>
|
||||
</li>
|
||||
<template v-if="this.Peer.private_key">
|
||||
<li>
|
||||
<a class="dropdown-item d-flex" role="button">
|
||||
<i class="me-auto bi bi-download"></i> Download
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item d-flex" role="button">
|
||||
<i class="me-auto bi bi-qr-code"></i> QR Code
|
||||
</a>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li>
|
||||
<a class="dropdown-item d-flex text-warning"
|
||||
|
||||
role="button">
|
||||
<i class="me-auto bi bi-lock"></i> Lock
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item d-flex fw-bold text-danger"
|
||||
|
||||
role="button">
|
||||
<i class="me-auto bi bi-trash"></i> Delete
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.dropdown-menu{
|
||||
right: 1rem;
|
||||
}
|
||||
</style>
|
@ -11,6 +11,8 @@ import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.
|
||||
import Setup from "@/views/setup.vue";
|
||||
import NewConfiguration from "@/views/newConfiguration.vue";
|
||||
import Configuration from "@/views/configuration.vue";
|
||||
import PeerSettings from "@/components/configurationComponents/peerSettings.vue";
|
||||
import PeerList from "@/components/configurationComponents/peerList.vue";
|
||||
|
||||
const checkAuth = async () => {
|
||||
let result = false
|
||||
@ -48,9 +50,18 @@ const router = createRouter({
|
||||
},
|
||||
{
|
||||
name: "Configuration",
|
||||
path: '/configuration/:id',
|
||||
component: Configuration
|
||||
}
|
||||
path: '/configuration/:id/',
|
||||
component: Configuration,
|
||||
children: [
|
||||
|
||||
{
|
||||
name: "Peer Settings",
|
||||
path: 'peer_settings',
|
||||
component: PeerSettings
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -61,429 +61,20 @@ import PeerSearch from "@/components/configurationComponents/peerSearch.vue";
|
||||
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
||||
import {ref} from "vue";
|
||||
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
|
||||
import PeerList from "@/components/configurationComponents/peerList.vue";
|
||||
|
||||
export default {
|
||||
name: "configuration",
|
||||
setup(){
|
||||
const dashboardConfigurationStore = DashboardConfigurationStore();
|
||||
const wireguardConfigurationStore = WireguardConfigurationsStore();
|
||||
return {dashboardConfigurationStore, wireguardConfigurationStore}
|
||||
},
|
||||
components: {PeerSearch, Peer, Line, Bar},
|
||||
data(){
|
||||
return {
|
||||
loading: false,
|
||||
error: null,
|
||||
configurationInfo: [],
|
||||
configurationPeers: [],
|
||||
historyDataSentDifference: [],
|
||||
historyDataReceivedDifference: [],
|
||||
|
||||
|
||||
historySentData: {
|
||||
labels: [],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Data Sent',
|
||||
data: [],
|
||||
fill: false,
|
||||
borderColor: '#198754',
|
||||
tension: 0
|
||||
},
|
||||
],
|
||||
},
|
||||
historyReceiveData: {
|
||||
labels: [],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Data Received',
|
||||
data: [],
|
||||
fill: false,
|
||||
borderColor: '#0d6efd',
|
||||
tension: 0
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'$route.params': {
|
||||
immediate: true,
|
||||
handler(){
|
||||
clearInterval(this.interval)
|
||||
this.loading = true;
|
||||
let id = this.$route.params.id;
|
||||
this.configurationInfo = [];
|
||||
this.configurationPeers = [];
|
||||
if (id){
|
||||
this.getPeers(id)
|
||||
this.setInterval();
|
||||
}
|
||||
}
|
||||
},
|
||||
'dashboardConfigurationStore.Configuration.Server.dashboard_refresh_interval'(){
|
||||
clearInterval(this.interval);
|
||||
this.setInterval();
|
||||
}
|
||||
},
|
||||
beforeRouteLeave(){
|
||||
clearInterval(this.interval)
|
||||
},
|
||||
methods:{
|
||||
getPeers(id){
|
||||
fetchGet("/api/getWireguardConfigurationInfo",
|
||||
{
|
||||
configurationName: id
|
||||
}, (res) => {
|
||||
this.configurationInfo = res.data.configurationInfo;
|
||||
this.configurationPeers = res.data.configurationPeers;
|
||||
this.loading = false;
|
||||
if (this.configurationPeers.length > 0){
|
||||
const sent = this.configurationPeers.map(x => x.total_sent + x.cumu_sent).reduce((x,y) => x + y).toFixed(4);
|
||||
const receive = this.configurationPeers.map(x => x.total_receive + x.cumu_receive).reduce((x,y) => x + y).toFixed(4);
|
||||
if (
|
||||
this.historyDataSentDifference[this.historyDataSentDifference.length - 1] !== sent
|
||||
){
|
||||
if (this.historyDataSentDifference.length > 0){
|
||||
this.historySentData = {
|
||||
labels: [...this.historySentData.labels, dayjs().format("HH:mm:ss A")],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Data Sent',
|
||||
data: [...this.historySentData.datasets[0].data,
|
||||
((sent - this.historyDataSentDifference[this.historyDataSentDifference.length - 1])*1000).toFixed(4)],
|
||||
fill: false,
|
||||
borderColor: ' #198754',
|
||||
tension: 0
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
this.historyDataSentDifference.push(sent)
|
||||
}
|
||||
if (
|
||||
this.historyDataReceivedDifference[this.historyDataReceivedDifference.length - 1] !== receive
|
||||
){
|
||||
if (this.historyDataReceivedDifference.length > 0){
|
||||
this.historyReceiveData = {
|
||||
labels: [...this.historyReceiveData.labels, dayjs().format("HH:mm:ss A")],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Data Received',
|
||||
data: [...this.historyReceiveData.datasets[0].data,
|
||||
((receive - this.historyDataReceivedDifference[this.historyDataReceivedDifference.length - 1])*1000).toFixed(4)],
|
||||
fill: false,
|
||||
borderColor: '#0d6efd',
|
||||
tension: 0
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
this.historyDataReceivedDifference.push(receive)
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
setInterval(){
|
||||
console.log('Set Interval');
|
||||
this.interval = setInterval(() => {
|
||||
this.getPeers(this.$route.params.id)
|
||||
}, parseInt(this.dashboardConfigurationStore.Configuration.Server.dashboard_refresh_interval))
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
configurationSummary(){
|
||||
return {
|
||||
connectedPeers: this.configurationPeers.filter(x => x.status === "running").length,
|
||||
totalUsage: this.configurationPeers.length > 0 ? this.configurationPeers.map(x => x.total_data + x.cumu_data).reduce((a, b) => a + b) : 0,
|
||||
totalReceive: this.configurationPeers.length > 0 ? this.configurationPeers.map(x => x.total_receive + x.cumu_receive).reduce((a, b) => a + b) : 0,
|
||||
totalSent: this.configurationPeers.length > 0 ? this.configurationPeers.map(x => x.total_sent + x.cumu_sent).reduce((a, b) => a + b) : 0
|
||||
}
|
||||
},
|
||||
receiveData(){
|
||||
return this.historyReceiveData
|
||||
},
|
||||
sentData(){
|
||||
return this.historySentData
|
||||
},
|
||||
individualDataUsage(){
|
||||
return {
|
||||
labels: this.configurationPeers.map(x => {
|
||||
if (x.name) return x.name
|
||||
return `Untitled Peer - ${x.id}`
|
||||
}),
|
||||
datasets: [{
|
||||
label: 'Total Data Usage',
|
||||
data: this.configurationPeers.map(x => x.cumu_data + x.total_data),
|
||||
backgroundColor: this.configurationPeers.map(x => `#0dcaf0`),
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: (tooltipItem) => {
|
||||
console.log(tooltipItem)
|
||||
return `${tooltipItem.formattedValue} GB`
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
},
|
||||
individualDataUsageChartOption(){
|
||||
return {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
ticks: {
|
||||
display: false,
|
||||
},
|
||||
grid: {
|
||||
display: false
|
||||
},
|
||||
},
|
||||
y:{
|
||||
ticks: {
|
||||
callback: (val, index) => {
|
||||
return `${val} GB`
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
display: false
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
chartOptions() {
|
||||
return {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: (tooltipItem) => {
|
||||
console.log(tooltipItem)
|
||||
return `${tooltipItem.formattedValue} MB/s`
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
ticks: {
|
||||
display: false,
|
||||
},
|
||||
grid: {
|
||||
display: false
|
||||
},
|
||||
},
|
||||
y:{
|
||||
ticks: {
|
||||
callback: (val, index) => {
|
||||
return `${val} MB/s`
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
display: false
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
searchPeers(){
|
||||
const fuse = new Fuse(this.configurationPeers, {
|
||||
keys: ["name", "id", "allowed_ip"]
|
||||
});
|
||||
|
||||
const result = this.wireguardConfigurationStore.searchString ? fuse.search(this.wireguardConfigurationStore.searchString).map(x => x.item) : this.configurationPeers;
|
||||
|
||||
return result.slice().sort((a, b) => {
|
||||
if ( a[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort]
|
||||
< b[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort] ){
|
||||
return -1;
|
||||
}
|
||||
if ( a[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort]
|
||||
> b[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort]){
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
components: {PeerList},
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mt-5 text-body" v-if="!loading">
|
||||
<div>
|
||||
<small CLASS="text-muted">CONFIGURATION</small>
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<h1 class="mb-0"><samp>{{this.configurationInfo.Name}}</samp></h1>
|
||||
<div class="dot active ms-0"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3 gy-2 gx-2 mb-2">
|
||||
<div class="col-6 col-lg-3">
|
||||
<div class="card rounded-3 bg-transparent shadow-sm">
|
||||
<div class="card-body py-2">
|
||||
<p class="mb-0 text-muted"><small>Address</small></p>
|
||||
{{this.configurationInfo.Address}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-lg-3">
|
||||
<div class="card rounded-3 bg-transparent shadow-sm">
|
||||
<div class="card-body py-2">
|
||||
<p class="mb-0 text-muted"><small>Listen Port</small></p>
|
||||
{{this.configurationInfo.ListenPort}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="word-break: break-all" class="col-12 col-lg-6">
|
||||
<div class="card rounded-3 bg-transparent shadow-sm">
|
||||
<div class="card-body py-2">
|
||||
<p class="mb-0 text-muted"><small>Public Key</small></p>
|
||||
<samp>{{this.configurationInfo.PublicKey}}</samp>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row gx-2 gy-2 mb-2">
|
||||
<div class="col-6 col-lg-3">
|
||||
<div class="card rounded-3 bg-transparent shadow-sm">
|
||||
<div class="card-body d-flex">
|
||||
<div>
|
||||
<p class="mb-0 text-muted"><small>Connected Peers</small></p>
|
||||
<strong class="h4">{{configurationSummary.connectedPeers}}</strong>
|
||||
</div>
|
||||
<i class="bi bi-ethernet ms-auto h2 text-muted"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-lg-3">
|
||||
<div class="card rounded-3 bg-transparent shadow-sm">
|
||||
<div class="card-body d-flex">
|
||||
<div>
|
||||
<p class="mb-0 text-muted"><small>Total Usage</small></p>
|
||||
<strong class="h4">{{configurationSummary.totalUsage.toFixed(4)}} GB</strong>
|
||||
</div>
|
||||
<i class="bi bi-arrow-down-up ms-auto h2 text-muted"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-lg-3">
|
||||
<div class="card rounded-3 bg-transparent shadow-sm">
|
||||
<div class="card-body d-flex">
|
||||
<div>
|
||||
<p class="mb-0 text-muted"><small>Total Received</small></p>
|
||||
<strong class="h4 text-primary">{{configurationSummary.totalReceive.toFixed(4)}} GB</strong>
|
||||
</div>
|
||||
<i class="bi bi-arrow-down ms-auto h2 text-muted"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-lg-3">
|
||||
<div class="card rounded-3 bg-transparent shadow-sm">
|
||||
<div class="card-body d-flex">
|
||||
<div>
|
||||
<p class="mb-0 text-muted"><small>Total Sent</small></p>
|
||||
<strong class="h4 text-success">{{configurationSummary.totalSent.toFixed(4)}} GB</strong>
|
||||
</div>
|
||||
<i class="bi bi-arrow-up ms-auto h2 text-muted"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row gx-2 gy-2 mb-5">
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="card rounded-3 bg-transparent shadow-sm">
|
||||
<div class="card-header bg-transparent border-0"><small class="text-muted">Peers Total Data Usage</small></div>
|
||||
<div class="card-body pt-1">
|
||||
<Bar
|
||||
:data="individualDataUsage"
|
||||
:options="individualDataUsageChartOption"
|
||||
style="height: 200px; width: 100%"></Bar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm col-lg-3">
|
||||
<div class="card rounded-3 bg-transparent shadow-sm">
|
||||
<div class="card-header bg-transparent border-0"><small class="text-muted">Real Time Received Data Usage</small></div>
|
||||
<div class="card-body pt-1">
|
||||
<Line
|
||||
:options="chartOptions"
|
||||
:data="receiveData"
|
||||
style="width: 100%; height: 200px"
|
||||
></Line>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm col-lg-3">
|
||||
<div class="card rounded-3 bg-transparent shadow-sm">
|
||||
<div class="card-header bg-transparent border-0"><small class="text-muted">Real Time Sent Data Usage</small></div>
|
||||
<div class="card-body pt-1">
|
||||
<Line
|
||||
:options="chartOptions"
|
||||
:data="sentData"
|
||||
style="width: 100%; height: 200px"
|
||||
></Line>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="d-flex align-items-center gap-3 mb-2 ">
|
||||
<h3>Peers</h3>
|
||||
<a href="#"
|
||||
class="ms-auto text-secondary text-decoration-none"><i class="bi bi-sliders2 me-2"></i>Peer Settings</a>
|
||||
<a href="#" class="text-decoration-none"><i class="bi bi-plus-circle-fill me-2"></i>Add Peer</a>
|
||||
</div>
|
||||
<PeerSearch></PeerSearch>
|
||||
|
||||
<TransitionGroup name="list" tag="div" class="row gx-2 gy-2 z-0">
|
||||
<div class="col-12 col-lg-6 col-xl-4"
|
||||
:key="peer.id"
|
||||
v-for="peer in this.searchPeers">
|
||||
<Peer :Peer="peer"></Peer>
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
<div class="mt-5 text-body">
|
||||
<PeerList></PeerList>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.peerNav .nav-link{
|
||||
&.active{
|
||||
//background: linear-gradient(var(--degree), var(--brandColor1) var(--distance2), var(--brandColor2) 100%);
|
||||
//color: white;
|
||||
background-color: #efefef;
|
||||
}
|
||||
}
|
||||
|
||||
.list-move, /* apply transition to moving elements */
|
||||
.list-enter-active,
|
||||
.list-leave-active {
|
||||
transition: all 0.4s cubic-bezier(0.82, 0.58, 0.17, 0.9);
|
||||
}
|
||||
|
||||
.list-leave-active{
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.list-enter-from,
|
||||
.list-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
|
||||
/* ensure leaving items are taken out of layout flow so that moving
|
||||
animations can be calculated correctly. */
|
||||
.list-leave-active {
|
||||
position: absolute;
|
||||
}
|
||||
</style>
|
@ -1078,3 +1078,18 @@ pre.index-alert {
|
||||
.message-leave-active {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.fade3-enter-active,
|
||||
.fade3-leave-active {
|
||||
transition: all 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
.fade3-enter-from{
|
||||
transform: scale(1);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.fade3-leave-to {
|
||||
transform: scale(0.8);
|
||||
opacity: 0;
|
||||
}
|
Loading…
Reference in New Issue
Block a user