Merge branch 'UX'
After Width: | Height: | Size: 65 KiB |
After Width: | Height: | Size: 64 KiB |
After Width: | Height: | Size: 114 KiB |
After Width: | Height: | Size: 65 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 93 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 676 B |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 15 KiB |
@ -0,0 +1 @@
|
||||
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
@ -0,0 +1 @@
|
||||
<svg id="Layer_1" enable-background="new 0 0 512 512" height="512" viewBox="0 0 512 512" width="512" fill="#FFFFFF" xmlns="http://www.w3.org/2000/svg"><path d="m219.141 64.449c-3.875-1.623-8.677-.058-10.567 4.525-2.15 5.263 1.736 11.028 7.403 11.028 8.594 0 11.327-12.134 3.164-15.553z"/><path d="m434.971 179.113c1.365-94.939-77.738-179.113-178.991-179.113-102.004 0-180.37 85.092-178.992 179.111.06 4.406-2.873-.208 125.447 141.932-29.634 18.107-49.455 50.76-49.455 87.957 0 56.794 46.206 103 103 103s103-46.206 103-103c0-48.459-33.641-89.202-78.794-100.12l38.877-128.664c26.445-25.864 68.254-27.721 96.973-1.032l-103.793 114.97c-4.668 5.17-.918 13.361 5.936 13.361 2.185 0 4.361-.89 5.94-2.639 117.669-130.34 110.792-121.404 110.852-125.763zm-339.01.113c29.148-26.658 70.325-24.908 96.938 1l38.874 128.655c-4.995 1.208-9.848 2.78-14.531 4.688zm247.019 229.774c0 47.972-39.028 87-87 87s-87-39.028-87-87 39.028-87 87-87 87 39.028 87 87zm-95.262-102.668-38.671-127.982c27.723-24.935 67.878-23.912 93.874-.031l-38.68 128.012c-5.504-.438-10.931-.445-16.523.001zm64.32-141.331c-28.257-24.554-70.626-29.081-104.779-5.534-.159-18.163 1.899-36.181 6.169-53.749 1.044-4.293-1.591-8.62-5.884-9.663-4.294-1.044-8.619 1.591-9.663 5.884-4.499 18.508-6.713 37.475-6.632 56.594-30.242-19.978-67.736-19.347-97.188 1.637 8.696-75.247 68.846-135.043 144.25-143.209-7.39 9.761-14.017 20.063-19.757 30.807-2.082 3.897-.611 8.744 3.286 10.826 3.881 2.074 8.735.627 10.826-3.286 6.59-12.335 14.43-24.05 23.329-34.956 26.91 32.962 43.324 72.622 47.599 114.993.416 4.123 3.893 7.198 7.95 7.198 4.757 0 8.444-4.101 7.969-8.803-4.452-44.127-20.963-83.933-45.842-116.775 75.383 8.175 135.514 67.949 144.223 143.173-33.431-23.835-75.878-20.567-105.856 4.863z"/><path d="m253.98 373v-3c0-4.418-3.582-8-8-8s-8 3.582-8 8v3h-5c-4.418 0-8 3.582-8 8s3.582 8 8 8h1v40h-1c-4.418 0-8 3.582-8 8s3.582 8 8 8h5v3c0 4.418 3.582 8 8 8s8-3.582 8-8v-3h4v3c0 4.418 3.582 8 8 8s8-3.582 8-8v-4.933c13.278-5.979 17.193-22.9 7.958-34.067 9.242-11.175 5.308-28.094-7.958-34.067v-4.933c0-4.418-3.582-8-8-8s-8 3.582-8 8v3zm17 50c0 3.308-2.691 6-6 6h-15v-12h15c3.308 0 6 2.692 6 6zm-21-22v-12h15c3.309 0 6 2.692 6 6s-2.691 6-6 6z"/><path d="m182.98 409c0 40.252 32.748 73 73 73s73-32.748 73-73-32.748-73-73-73-73 32.748-73 73zm130 0c0 31.43-25.57 57-57 57s-57-25.57-57-57 25.57-57 57-57 57 25.57 57 57z"/></svg>
|
After Width: | Height: | Size: 2.3 KiB |
@ -0,0 +1 @@
|
||||
<svg height="512" viewBox="0 0 64 64" width="512" fill="#FFFFFF" xmlns="http://www.w3.org/2000/svg"><g id="Business-cryptocurrency-digital-money-3" data-name="Business-cryptocurrency-digital-money"><path d="m61 6h-58a1 1 0 0 0 -1 1v50a1 1 0 0 0 1 1h58a1 1 0 0 0 1-1v-50a1 1 0 0 0 -1-1zm-1 50h-56v-40h56zm0-42h-56v-6h56z"/><circle cx="8" cy="11" r="2"/><circle cx="14" cy="11" r="2"/><circle cx="20" cy="11" r="2"/><path d="m56 50v-23a1 1 0 0 0 -1-1h-6a1 1 0 0 0 -1 1v23h-4v-15a1 1 0 0 0 -1-1h-6a1 1 0 0 0 -1 1v15h-4v-9a1 1 0 0 0 -1-1h-6a1 1 0 0 0 -1 1v9h-6v-10.05a11 11 0 1 0 -2 0v11.05a1 1 0 0 0 1 1h41v-2zm-48-21a9 9 0 1 1 9 9 9.014 9.014 0 0 1 -9-9zm22 21h-4v-8h4zm12 0h-4v-14h4zm12 0h-4v-22h4z"/><path d="m22 27a3.01 3.01 0 0 0 -2-2.82v-1.18h-2v1h-2v-1h-2v1h-2v2h1v6h-1v2h2v1h2v-1h2v1h2v-1.18a3.01 3.01 0 0 0 2-2.82 2.974 2.974 0 0 0 -.78-2 2.974 2.974 0 0 0 .78-2zm-3 5h-4v-2h4a1 1 0 0 1 0 2zm0-4h-4v-2h4a1 1 0 0 1 0 2z"/><path d="m6 43h2v2h-2z"/><path d="m10 43h4v2h-4z"/><path d="m6 47h8v2h-8z"/></g></svg>
|
After Width: | Height: | Size: 1013 B |
@ -0,0 +1,2 @@
|
||||
<svg height="512" viewBox="0 0 64 64" width="512" xmlns="http://www.w3.org/2000/svg"><path d="m50 2h-32a5.006 5.006 0 0 0 -5 5v45h-4v5a5.006 5.006 0 0 0 5 5h28a5.006 5.006 0 0 0 5-5v-31h7a1 1 0 0 0 1-1v-18a5.006 5.006 0 0 0 -5-5zm-36 58a3 3 0 0 1 -3-3v-3h26v3a4.98 4.98 0 0 0 1 3zm31-53v50a3 3 0 0 1 -6 0v-5h-24v-45a3 3 0 0 1 3-3h28.026a4.948 4.948 0 0 0 -1.026 3zm8 17h-6v-17a3 3 0 0 1 6 0z"/><path d="m18 25h24v2h-24z"/><path d="m18 29h24v2h-24z"/><path d="m18 33h24v2h-24z"/><path d="m18 37h24v2h-24z"/><path d="m27 22h2v-2h2v2h2v-2a2.987 2.987 0 0 0 2.22-5 2.987 2.987 0 0 0 -2.22-5v-2h-2v2h-2v-2h-2v2h-2v2h2v6h-2v2h2zm2-10h4a1 1 0 0 1 0 2h-4zm0 4h4a1 1 0 0 1 0 2h-4z"/><path d="m18 41h24v2h-24z"/><path d="m18 11h2v-3h3v-2h-5z"/><path d="m41 11h2v-5h-5v2h3z"/><path d="m41 21h2v2h-2z"/><path d="m38 17h2v2h-2z"/><path d="m18 45h14v2h-14z"/></svg>
|
||||
<div>Icons made by <a href="https://www.flaticon.com/authors/smalllikeart" title="smalllikeart">smalllikeart</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a></div>
|
After Width: | Height: | Size: 1.0 KiB |
@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?><!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --><svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve"><g><g><path d="M400.388,175.787c-1.707-3.413-4.267-5.12-7.68-5.12H292.015L391.855,12.8c1.707-2.56,1.707-5.973,0-8.533 S387.588,0,384.175,0H247.642c-3.413,0-5.973,1.707-7.68,4.267l-128,256c-1.707,2.56-1.707,5.973,0,8.533 c1.707,2.56,5.12,4.267,7.68,4.267h87.893l-95.573,227.84c-1.707,3.413,0,7.68,3.413,10.24c0.853,0.853,2.56,0.853,4.267,0.853 c2.56,0,5.12-0.853,6.827-2.56l273.067-324.267C401.242,182.613,402.095,179.2,400.388,175.787z M149.508,454.827l78.507-187.733 c0.853-2.56,0.853-5.12-0.853-7.68c-1.707-1.707-4.267-3.413-6.827-3.413h-87.04L252.762,17.067h116.053L268.122,174.933 c-1.707,2.56-1.707,5.973,0,8.533s4.267,4.267,7.68,4.267h98.987L149.508,454.827z"/></g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g></svg>
|
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1 @@
|
||||
<svg height="512" viewBox="0 0 64 64" width="512" xmlns="http://www.w3.org/2000/svg"><path d="m30 12a18 18 0 1 0 18 18 18.021 18.021 0 0 0 -18-18zm0 34a16 16 0 1 1 16-16 16.019 16.019 0 0 1 -16 16z"/><path d="m60.9 53.833-12.221-12.228a22.029 22.029 0 1 0 -7.079 7.074l12.233 12.221a5 5 0 0 0 7.067-7.067zm-50.9-23.833a20 20 0 1 1 20 20 20.023 20.023 0 0 1 -20-20zm49.483 29.483a3.067 3.067 0 0 1 -4.236 0l-11.967-11.958a22.2 22.2 0 0 0 4.245-4.245l11.958 11.967a3 3 0 0 1 0 4.236z"/><path d="m29 2h2v4h-2z"/><path d="m22.271 2.886h2v4h-2z" transform="matrix(.966 -.259 .259 .966 -.472 6.191)"/><path d="m16 5.483h2v4h-2z" transform="matrix(.866 -.5 .5 .866 -1.464 9.503)"/><path d="m10.615 9.615h2v4h-2z" transform="matrix(.707 -.707 .707 .707 -4.811 11.615)"/><path d="m6.483 15h2v4h-2z" transform="matrix(.5 -.866 .866 .5 -10.981 14.98)"/><path d="m3.886 21.271h2v4h-2z" transform="matrix(.259 -.966 .966 .259 -18.856 21.965)"/><path d="m2 29h4v2h-4z"/><path d="m2.886 35.729h4v2h-4z" transform="matrix(.966 -.259 .259 .966 -9.343 2.517)"/><path d="m5.483 42h4v2h-4z" transform="matrix(.866 -.5 .5 .866 -20.498 9.503)"/><path d="m9.615 47.385h4v2h-4z" transform="matrix(.707 -.707 .707 .707 -30.811 22.385)"/><path d="m15 51.516h4v2h-4z" transform="matrix(.5 -.866 .866 .5 -36.982 40.986)"/><path d="m21.271 54.114h4v2h-4z" transform="matrix(.259 -.966 .966 .259 -35.988 63.327)"/><path d="m29 54h2v4h-2z"/><path d="m35.73 53.114h2v4h-2z" transform="matrix(.966 -.259 .259 .966 -13.012 11.383)"/><path d="m54.114 34.729h2v4h-2z" transform="matrix(.259 -.966 .966 .259 5.367 80.454)"/><path d="m54 29h4v2h-4z"/><path d="m53.114 22.271h4v2h-4z" transform="matrix(.966 -.259 .259 .966 -4.146 15.064)"/><path d="m50.516 16h4.001v2h-4.001z" transform="matrix(.866 -.5 .5 .866 -1.465 28.531)"/><path d="m46.385 10.615h4v2h-4z" transform="matrix(.707 -.707 .707 .707 5.958 37.615)"/><path d="m41 6.483h4v2h-4z" transform="matrix(.5 -.866 .866 .5 15.022 40.983)"/><path d="m34.729 3.886h4v2h-4z" transform="matrix(.259 -.966 .966 .259 22.504 39.099)"/><path d="m30 19a11 11 0 1 0 11 11 11.013 11.013 0 0 0 -11-11zm0 20a9 9 0 1 1 9-9 9.011 9.011 0 0 1 -9 9z"/><path d="m32 25v-2h-2v2h-2v-2h-2v2a1 1 0 0 0 -1 1v8a1 1 0 0 0 1 1v2h2v-2h2v2h2v-2a2.987 2.987 0 0 0 2.22-5 2.987 2.987 0 0 0 -2.22-5zm1 3a1 1 0 0 1 -1 1h-5v-2h5a1 1 0 0 1 1 1zm-1 5h-5v-2h5a1 1 0 0 1 0 2z"/></svg>
|
After Width: | Height: | Size: 2.3 KiB |
@ -0,0 +1,97 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 479.504 479.504" style="enable-background:new 0 0 479.504 479.504;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M392.984,144.622v0.096c-52.497-85.63-164.47-112.489-250.1-59.993c-28.375,17.396-51.433,42.245-66.66,71.841
|
||||
l-1.424-3.304c-36.8,15.888-61.248,36.2-70.76,58.752l14.712,6.216c6.688-15.864,24.12-31.312,49.6-44.248
|
||||
c-14.202,36.39-16.272,76.394-5.904,114.056c-27.832-14.584-44.36-32.288-46.568-50.4L0,239.558
|
||||
c3.136,25.84,25.032,48.968,63.32,66.896l2.864-6.264c33.456,94.761,137.397,144.458,232.158,111.002
|
||||
c94.761-33.456,144.458-137.397,111.002-232.158c-1.507-4.267-3.172-8.477-4.992-12.62c24.232,10.696,42.352,23.8,51.672,37.776
|
||||
l13.312-8.872C455.872,175.126,429.472,157.598,392.984,144.622z M351.072,118.83l-6.872,4.584l-26.248-5.256l-17.544-5.848
|
||||
c-1.442-0.493-2.996-0.555-4.472-0.176l-21.736,5.432l-7.04-2.344l5.664-11.328h19.056c1.241,0.001,2.465-0.286,3.576-0.84
|
||||
l19.8-9.904C328.278,100.088,340.322,108.724,351.072,118.83z M172.008,87.614l13.432,8.952c0.942,0.625,2.005,1.045,3.12,1.232
|
||||
l26.768,4.464l-1.776,5.32l-14.208,4.744c-1.809,0.602-3.344,1.83-4.328,3.464l-16.968,28.288l-26.904,16.144l-40.4,5.768
|
||||
c-3.939,0.565-6.865,3.94-6.864,7.92v18c-0.002,2.12,0.839,4.155,2.336,5.656l9.664,9.656v5.744L94.6,198.774l-8.56-25.664
|
||||
C102.963,134.841,133.646,104.327,172.008,87.614z M77.456,197.91v0.04l2.832,8.488c0.562,1.688,1.671,3.141,3.152,4.128
|
||||
l27.88,18.584l-5.104,5.104c-1.497,1.501-2.338,3.536-2.336,5.656v18c-0.001,1.241,0.286,2.465,0.84,3.576l12,24
|
||||
c1.108,2.21,3.175,3.784,5.6,4.264l23.576,4.712v20.296c-1.808-0.328-3.68-0.592-5.448-0.944
|
||||
c-16.601-3.152-32.944-7.532-48.896-13.104l-3.576,10C71.072,275.584,67.339,235.557,77.456,197.91z M237.88,405.91
|
||||
c-61.496-0.06-117.926-34.092-146.672-88.456c15.106,5.027,30.526,9.057,46.16,12.064c2.768,0.544,5.672,0.984,8.512,1.48v16.912
|
||||
c-0.001,1.016,0.192,2.024,0.568,2.968l12,30c0.874,2.184,2.665,3.872,4.896,4.616l18,6c2.435,0.814,5.114,0.409,7.2-1.088
|
||||
c2.09-1.504,3.329-3.921,3.328-6.496v-26l20.8-15.6c1.377-1.111,2.336-2.657,2.72-4.384c3.88,0.112,7.744,0.264,11.656,0.296
|
||||
h5.336c2.952,0,5.92,0,8.896-0.08c13.768-0.224,27.504-1.048,40.992-2.288c4.629,11.857,16.072,19.647,28.8,19.608h0.48
|
||||
c16.03-0.316,29.173-12.807,30.304-28.8c4.72-1.04,9.44-2.096,13.992-3.272v0.456c-0.002,2.12,0.839,4.155,2.336,5.656l11.2,11.2
|
||||
C338.103,381.739,289.48,405.849,237.88,405.91z M296.04,324.702c-0.147-8.296,6.46-15.141,14.756-15.287
|
||||
c0.012,0,0.024,0,0.036-0.001h0.256c8.311,0.001,15.047,6.739,15.046,15.05c-0.001,8.311-6.739,15.047-15.05,15.046
|
||||
C302.868,339.509,296.171,332.918,296.04,324.702z M378.68,327.406l-6.8-6.808v-1.664c4.88-1.496,9.6-3.096,14.24-4.744
|
||||
C383.84,318.71,381.352,323.118,378.68,327.406z M394.752,293.726c-7.232,3.008-14.856,5.803-22.872,8.384v-2.2
|
||||
c-0.001-1.242-0.292-2.466-0.848-3.576l-23.152-46.312V227.91c-0.001-2.676-1.34-5.174-3.568-6.656l-18-12
|
||||
c-1.881-1.256-4.206-1.657-6.4-1.104l-24,6l3.88,15.52l20.568-5.144l11.496,7.664v19.72c-0.001,1.241,0.286,2.465,0.84,3.576
|
||||
l23.16,46.312v5.032c-5.493,1.488-11.133,2.872-16.92,4.152c-1.565-3.279-3.693-6.257-6.288-8.8
|
||||
c-5.945-5.686-13.879-8.818-22.104-8.728c-15.223,0.208-28.017,11.491-30.128,26.568c-12.968,1.192-26.176,1.976-39.408,2.192
|
||||
c-2.896,0.048-5.776,0.08-8.64,0.08h-5.152c-3.832,0-7.592-0.176-11.36-0.288v-0.712l21.656-21.656l-11.32-11.312l-24,24
|
||||
c-1.497,1.501-2.338,3.536-2.336,5.656v3.344c-0.272,0-0.552,0-0.8-0.048l-0.76,11.832l-19.2,14.4c-2.014,1.511-3.2,3.882-3.2,6.4
|
||||
v18.896l-3.952-1.312l-10.048-25.12v-12.88c3.936,0.544,7.856,1.12,11.864,1.6l1.792-15.944
|
||||
c-4.608-0.512-9.136-1.152-13.648-1.792V287.91c0.001-3.802-2.675-7.08-6.4-7.84l-26.168-5.24l-9.4-18.808v-12.8l19.808-19.808
|
||||
l7.032,14.072c1.493,2.984,4.682,4.729,8,4.376l33.88-3.592l5.296,5.296l11.312-11.312l-8-8c-1.708-1.706-4.095-2.55-6.496-2.296
|
||||
l-32.16,3.416l-9.52-19.04c-1.98-3.95-6.787-5.546-10.737-3.566c-0.763,0.382-1.459,0.884-2.063,1.486l-4.336,4.344v-4.688
|
||||
c0-2.122-0.844-4.156-2.344-5.656l-9.656-9.656v-7.752l35.128-5.016c1.056-0.151,2.071-0.513,2.984-1.064l30-18
|
||||
c1.125-0.673,2.067-1.613,2.744-2.736l16.48-27.464l15.2-5.072c2.391-0.79,4.266-2.665,5.056-5.056l6-18
|
||||
c1.386-4.195-0.891-8.72-5.086-10.106c-0.387-0.128-0.784-0.226-1.186-0.294l-34.304-5.72l-2.2-1.464
|
||||
c15.287-4.575,31.155-6.914,47.112-6.944c19.986,0.034,39.8,3.704,58.472,10.832l-6.336,3.2H267.88
|
||||
c-3.032-0.001-5.805,1.712-7.16,4.424l-12,24c-1.973,3.953-0.367,8.758,3.586,10.73c0.335,0.167,0.682,0.311,1.038,0.43l18,6
|
||||
c1.443,0.486,2.995,0.547,4.472,0.176l21.736-5.432l15.792,5.256c0.313,0.111,0.634,0.197,0.96,0.256l30,6
|
||||
c0.527,0.106,1.063,0.16,1.6,0.16c1.578-0.001,3.12-0.469,4.432-1.344l12.088-8.056c5.627,6.38,10.754,13.184,15.336,20.352
|
||||
l-26.504,8.8l-30.624-5.104l-10.096-15.08c-1.484-2.225-3.982-3.56-6.656-3.56h-36c-2.124-0.002-4.162,0.842-5.664,2.344l-18,18
|
||||
l11.336,11.272l15.656-15.656h28.4l9.6,14.4c1.228,1.838,3.163,3.087,5.344,3.448l36,6c1.288,0.22,2.61,0.115,3.848-0.304
|
||||
l31.416-10.472C406.28,204.868,409.509,251.396,394.752,293.726z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M479.136,220.974l-15.888,1.872c0.163,1.416,0.249,2.839,0.256,4.264c0,19.152-15.936,38.72-44.856,55.104l7.88,13.92
|
||||
c34.656-19.624,52.976-43.488,52.976-69.024v-0.864C479.47,224.484,479.347,222.725,479.136,220.974z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M278.248,238.196c-1.062-3.07-2.742-5.89-4.936-8.285c8.869-9.683,8.209-24.723-1.475-33.592
|
||||
c-2.396-2.194-5.215-3.874-8.285-4.936v-9.472h-16v8h-16v-8h-16v8h-8v16h8v48h-8v16h8v8h16v-8h16v8h16v-9.472
|
||||
C275.961,264.145,282.541,250.605,278.248,238.196z M255.552,253.91h-24v-16h24c4.418,0,8,3.582,8,8S259.97,253.91,255.552,253.91
|
||||
z M255.552,221.91h-24v-16h24c4.418,0,8,3.582,8,8S259.97,221.91,255.552,221.91z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
||||
<div>Icons made by <a href="https://www.flaticon.com/authors/turkkub" title="turkkub">turkkub</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a></div>
|
After Width: | Height: | Size: 6.2 KiB |
@ -0,0 +1 @@
|
||||
<svg height="512" viewBox="0 0 64 64" width="512" xmlns="http://www.w3.org/2000/svg"><path d="m31 16a16 16 0 1 0 16 16 16.021 16.021 0 0 0 -16-16zm0 30a14 14 0 1 1 14-14 14.015 14.015 0 0 1 -14 14z"/><path d="m31 13a19 19 0 1 0 19 19 19.017 19.017 0 0 0 -19-19zm0 36a17 17 0 1 1 17-17 17.024 17.024 0 0 1 -17 17z"/><path d="m36.97 32a4.985 4.985 0 0 0 -2.97-9v-2h-2v2h-3v-2h-2v2h-3v2h2v14h-2v2h3v2h2v-2h3v2h2v-2a4.985 4.985 0 0 0 2.97-9zm-8.97-7h6a3 3 0 0 1 0 6h-6zm6 14h-6v-6h6a3 3 0 0 1 0 6z"/><path d="m8 14a6 6 0 1 0 -6-6 6.006 6.006 0 0 0 6 6zm0-10a4 4 0 1 1 -4 4 4 4 0 0 1 4-4z"/><path d="m56 14a6 6 0 1 0 -6-6 6.006 6.006 0 0 0 6 6zm0-10a4 4 0 1 1 -4 4 4 4 0 0 1 4-4z"/><path d="m8 50a6 6 0 1 0 6 6 6.006 6.006 0 0 0 -6-6zm0 10a4 4 0 1 1 4-4 4 4 0 0 1 -4 4z"/><path d="m56 50a6 6 0 1 0 6 6 6.006 6.006 0 0 0 -6-6zm0 10a4 4 0 1 1 4-4 4 4 0 0 1 -4 4z"/><path d="m44.964 14.5h7.071v2h-7.071z" transform="matrix(.707 -.707 .707 .707 3.245 38.835)"/><path d="m47.5 44.964h2v7.071h-2z" transform="matrix(.707 -.707 .707 .707 -20.089 48.5)"/><path d="m12.172 48h5.657v2h-5.657z" transform="matrix(.707 -.707 .707 .707 -30.255 24.958)"/><path d="m14 12.172h2v5.657h-2z" transform="matrix(.707 -.707 .707 .707 -6.213 15)"/></svg>
|
After Width: | Height: | Size: 1.2 KiB |
@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?><!-- Generator: Adobe Illustrator 22.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 60 60" style="enable-background:new 0 0 60 60;" xml:space="preserve" width="512" height="512"><g id="_x32_5-Bitcoin_Settings"> <path d="M34,60h-8c-0.552,0-1-0.448-1-1v-3.5c0-1.151-0.735-2.171-1.786-2.48c-1.62-0.477-3.194-1.128-4.679-1.938 c-0.971-0.529-2.216-0.331-3.028,0.482l-2.477,2.477c-0.39,0.391-1.024,0.391-1.414,0l-5.657-5.657 c-0.391-0.391-0.391-1.023,0-1.414l2.477-2.477c0.812-0.813,1.011-2.058,0.482-3.028c-0.809-1.485-1.461-3.059-1.937-4.679 C6.671,35.734,5.651,35,4.5,35H1c-0.552,0-1-0.448-1-1v-8c0-0.552,0.448-1,1-1h3.5c1.151,0,2.171-0.734,2.48-1.786 c0.477-1.62,1.128-3.194,1.937-4.679c0.529-0.97,0.33-2.215-0.482-3.028l-2.477-2.477c-0.391-0.391-0.391-1.023,0-1.414 l5.657-5.657c0.39-0.391,1.024-0.391,1.414,0l2.477,2.477c0.813,0.812,2.058,1.01,3.028,0.482c1.485-0.809,3.059-1.461,4.679-1.938 C24.265,6.671,25,5.651,25,4.5V1c0-0.552,0.448-1,1-1h8c0.552,0,1,0.448,1,1v3.5c0,1.151,0.735,2.171,1.786,2.48 c1.62,0.477,3.194,1.128,4.679,1.938c0.97,0.527,2.215,0.33,3.028-0.482l2.477-2.477c0.39-0.391,1.024-0.391,1.414,0l5.657,5.657 c0.391,0.391,0.391,1.023,0,1.414l-2.477,2.477c-0.813,0.813-1.011,2.058-0.482,3.028c0.809,1.485,1.461,3.059,1.937,4.679 C53.329,24.266,54.349,25,55.5,25H59c0.552,0,1,0.448,1,1v8c0,0.552-0.448,1-1,1h-3.5c-1.151,0-2.171,0.734-2.48,1.786 c-0.477,1.62-1.128,3.194-1.937,4.679c-0.529,0.97-0.33,2.215,0.482,3.028l2.477,2.477c0.391,0.391,0.391,1.023,0,1.414 l-5.657,5.657c-0.39,0.391-1.024,0.391-1.414,0l-2.477-2.477c-0.813-0.813-2.058-1.01-3.028-0.482 c-1.485,0.809-3.059,1.461-4.679,1.938C35.735,53.329,35,54.349,35,55.5V59C35,59.552,34.552,60,34,60z M27,58h6v-2.5 c0-2.032,1.325-3.841,3.221-4.399c1.485-0.437,2.927-1.033,4.287-1.774c1.743-0.95,3.964-0.611,5.399,0.824l1.77,1.77l4.243-4.243 l-1.77-1.77c-1.435-1.435-1.774-3.656-0.825-5.399c0.741-1.36,1.338-2.803,1.775-4.287C51.659,34.325,53.468,33,55.5,33H58v-6h-2.5 c-2.032,0-3.841-1.325-4.399-3.222c-0.437-1.484-1.034-2.926-1.775-4.287c-0.95-1.744-0.611-3.964,0.825-5.399l1.77-1.77 L47.678,8.08l-1.77,1.77c-1.435,1.436-3.655,1.775-5.399,0.824c-1.36-0.741-2.802-1.338-4.287-1.774C34.325,8.341,33,6.532,33,4.5 V2h-6v2.5c0,2.032-1.325,3.841-3.221,4.399c-1.485,0.437-2.927,1.033-4.287,1.774c-1.743,0.95-3.964,0.611-5.399-0.824l-1.77-1.77 L8.08,12.322l1.77,1.77c1.436,1.436,1.775,3.656,0.825,5.399c-0.741,1.36-1.338,2.803-1.775,4.287C8.341,25.675,6.532,27,4.5,27H2 v6h2.5c2.032,0,3.841,1.325,4.399,3.222c0.437,1.484,1.034,2.926,1.775,4.287c0.95,1.744,0.611,3.964-0.825,5.399l-1.77,1.77 l4.243,4.243l1.77-1.77c1.436-1.435,3.656-1.773,5.399-0.824c1.36,0.741,2.802,1.338,4.287,1.774C25.675,51.659,27,53.468,27,55.5 V58z"/> <path d="M30,48c-9.925,0-18-8.075-18-18s8.075-18,18-18s18,8.075,18,18S39.925,48,30,48z M30,14c-8.823,0-16,7.178-16,16 s7.177,16,16,16s16-7.178,16-16S38.823,14,30,14z"/> <path d="M17,31c-0.552,0-1-0.448-1-1c0-7.72,6.28-14,14-14c0.552,0,1,0.448,1,1s-0.448,1-1,1c-6.617,0-12,5.383-12,12 C18,30.552,17.552,31,17,31z"/> <path d="M30,44c-0.552,0-1-0.448-1-1s0.448-1,1-1c6.617,0,12-5.383,12-12c0-0.552,0.448-1,1-1s1,0.448,1,1C44,37.72,37.72,44,30,44 z"/> <path d="M32,31h-6c-0.552,0-1-0.448-1-1v-6c0-0.552,0.448-1,1-1h6c2.206,0,4,1.794,4,4S34.206,31,32,31z M27,29h5 c1.103,0,2-0.897,2-2s-0.897-2-2-2h-5V29z"/> <path d="M32,37h-6c-0.552,0-1-0.448-1-1v-6c0-0.552,0.448-1,1-1h6c2.206,0,4,1.794,4,4S34.206,37,32,37z M27,35h5 c1.103,0,2-0.897,2-2s-0.897-2-2-2h-5V35z"/> <path d="M28,25c-0.552,0-1-0.448-1-1v-3c0-0.552,0.448-1,1-1s1,0.448,1,1v3C29,24.552,28.552,25,28,25z"/> <path d="M32,25c-0.552,0-1-0.448-1-1v-3c0-0.552,0.448-1,1-1s1,0.448,1,1v3C33,24.552,32.552,25,32,25z"/> <path d="M28,40c-0.552,0-1-0.448-1-1v-3c0-0.552,0.448-1,1-1s1,0.448,1,1v3C29,39.552,28.552,40,28,40z"/> <path d="M32,40c-0.552,0-1-0.448-1-1v-3c0-0.552,0.448-1,1-1s1,0.448,1,1v3C33,39.552,32.552,40,32,40z"/> <path d="M26,25h-2c-0.552,0-1-0.448-1-1s0.448-1,1-1h2c0.552,0,1,0.448,1,1S26.552,25,26,25z"/> <path d="M26,37h-2c-0.552,0-1-0.448-1-1s0.448-1,1-1h2c0.552,0,1,0.448,1,1S26.552,37,26,37z"/></g></svg>
|
After Width: | Height: | Size: 4.2 KiB |
@ -0,0 +1 @@
|
||||
<svg height="512" viewBox="0 0 64 64" width="512" xmlns="http://www.w3.org/2000/svg"><path d="m32 24a16 16 0 1 0 16 16 16.021 16.021 0 0 0 -16-16zm0 30a14 14 0 1 1 14-14 14.015 14.015 0 0 1 -14 14z"/><path d="m60 22.03v-2.03a5 5 0 0 0 -5-5h-2.96a8.962 8.962 0 0 0 -11.65-4.24 8.986 8.986 0 0 0 -16.41-.82 8.979 8.979 0 0 0 -12.47 5.06h-4.51a5 5 0 0 0 -5 5v34a5 5 0 0 0 5 5h50a5 5 0 0 0 5-5v-28a4.952 4.952 0 0 0 -2-3.97zm-7.23-5.03h2.23a3.009 3.009 0 0 1 3 3v1.1a5.468 5.468 0 0 0 -1-.1h-4.23a8.811 8.811 0 0 0 0-4zm-11.87-4.26a6.967 6.967 0 0 1 9.59 3.63 7.033 7.033 0 0 1 .51 2.63 7.114 7.114 0 0 1 -.29 2h-13.07a8.96 8.96 0 0 0 3.36-7 8.8 8.8 0 0 0 -.1-1.26zm-8.9-5.74a7 7 0 1 1 -7 7 7.008 7.008 0 0 1 7-7zm-18.78 9.25a7 7 0 0 1 6.78-5.25 6.856 6.856 0 0 1 3.27.84 8.931 8.931 0 0 0 3.09 9.16h-12.67a6.939 6.939 0 0 1 -.47-4.75zm-9.22 3.75a3.009 3.009 0 0 1 3-3h4.06c-.03.33-.06.67-.06 1a8.981 8.981 0 0 0 .53 3h-7.53zm56 24h-7a2.006 2.006 0 0 1 -2-2v-6a2.006 2.006 0 0 1 2-2h7zm0-12h-7a4 4 0 0 0 -4 4v6a4 4 0 0 0 4 4h7v8a3.009 3.009 0 0 1 -3 3h-50a3.009 3.009 0 0 1 -3-3v-31h53a3.009 3.009 0 0 1 3 3z"/><path d="m37.97 40a4.985 4.985 0 0 0 -2.97-9v-2h-2v2h-3v-2h-2v2h-3v2h2v14h-2v2h3v2h2v-2h3v2h2v-2a4.985 4.985 0 0 0 2.97-9zm-8.97-7h6a3 3 0 0 1 0 6h-6zm6 14h-6v-6h6a3 3 0 0 1 0 6z"/><path d="m56 42a3 3 0 1 0 -3-3 3 3 0 0 0 3 3zm0-4a1 1 0 1 1 -1 1 1 1 0 0 1 1-1z"/><path d="m6 27v26a3 3 0 0 0 3 3h3v-2h-3a1 1 0 0 1 -1-1v-26a1 1 0 0 1 1-1h3v-2h-3a3 3 0 0 0 -3 3z"/></svg>
|
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 6.5 KiB |
@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?><svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve"><g> <g> <path d="M482,274.58V255c0-24.485-11.797-46.264-30.002-59.961c0-0.013,0.002-0.026,0.002-0.039v-30c0-41.355-33.645-75-75-75h-15 V45c0-24.813-20.187-45-45-45H135c-24.813,0-45,20.187-45,45v45H60c-33.084,0-60,26.916-60,60v287c0,41.355,33.645,75,75,75h332 c41.355,0,75-33.645,75-75v-17.58c17.459-6.192,30-22.865,30-42.42v-60C512,297.445,499.459,280.772,482,274.58z M362,120h15 c24.813,0,45,20.187,45,45v15c-6.569,0-52.366,0-60,0V120z M240,30h77c8.271,0,15,6.729,15,15v135h-92V30z M180,30h30v150h-30V30z M120,45c0-8.271,6.729-15,15-15h15v150h-30V45z M60,120h30v60H60c-16.542,0-30-13.458-30-30S43.458,120,60,120z M452,437 c0,24.813-20.187,45-45,45H75c-24.813,0-45-20.187-45-45V201.928C38.833,207.051,49.075,210,60,210c17.464,0,342.315,0,347,0 c24.813,0,45,20.187,45,45v17h-45c-41.355,0-75,33.645-75,75c0,41.355,33.645,75,75,75h45V437z M482,377c0,8.271-6.729,15-15,15 h-60c-24.813,0-45-20.187-45-45s20.187-45,45-45h60c8.271,0,15,6.729,15,15V377z"/> </g></g><g> <g> <circle cx="407" cy="347" r="15"/> </g></g><g> <g> <path d="M300,317c0-24.813-20.187-45-45-45h-15v-15c0-8.284-6.716-15-15-15s-15,6.716-15,15v15h-45c-8.284,0-15,6.716-15,15 s6.716,15,15,15h15v90h-15c-8.284,0-15,6.716-15,15s6.716,15,15,15h45v15c0,8.284,6.716,15,15,15s15-6.716,15-15v-15h15 c24.813,0,45-20.187,45-45c0-11.517-4.354-22.032-11.495-30C295.646,339.032,300,328.517,300,317z M255,392h-45v-30h45 c8.271,0,15,6.729,15,15S263.271,392,255,392z M255,332h-45v-30h45c8.271,0,15,6.729,15,15S263.271,332,255,332z"/> </g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g></svg>
|
After Width: | Height: | Size: 1.8 KiB |
@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 499.312 499.312" fill="#FFFFFF" style="enable-background:new 0 0 499.312 499.312;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M499.312,35.312L464,0l-83.28,83.28C356.6,57.904,323.088,43.312,288,43.312c-35.296,0-68.32,14.376-92.424,39.624
|
||||
C182.616,63.872,160.752,51.312,136,51.312H72c-39.704,0-72,32.304-72,72v96c0,13.232,10.768,24,24,24h40v68.28l19.448,12.96
|
||||
l-22.76,22.76l24,24l-24,24l24,24L64,440v13.528l27.472,45.784h25.064L144,453.528V243.312h38.232
|
||||
c23.92,35.12,63.096,56,105.768,56c70.576,0,128-57.424,128-128c0-15.36-2.752-30.352-8.064-44.624L499.312,35.312z M128,449.096
|
||||
l-20.528,34.216h-6.936L80,449.096v-2.472l27.312-27.312l-24-24l24-24l-24-24l25.24-25.24L80,303.032v-59.72h48V449.096z
|
||||
M192,219.312c0,4.408-3.584,8-8,8H24c-4.416,0-8-3.592-8-8v-96c0-30.88,25.128-56,56-56h64c30.872,0,56,25.12,56,56V219.312z
|
||||
M464,22.624l12.688,12.688L288,224l-44.688-44.688L256,166.624l32,32L464,22.624z M288,283.312
|
||||
c-35.648,0-68.512-16.64-89.632-44.896c5.816-4.384,9.632-11.28,9.632-19.104v-96c0-8.848-1.68-17.296-4.616-25.136
|
||||
C224.72,73.496,255.24,59.312,288,59.312c30.832,0,60.296,12.872,81.424,35.264L288,176l-32-32l-35.312,35.312L288,246.624
|
||||
L395.304,139.32c3.08,10.344,4.696,21.056,4.696,31.992C400,233.072,349.76,283.312,288,283.312z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M128,99.312H80c-17.648,0-32,14.352-32,32s14.352,32,32,32h48c17.648,0,32-14.352,32-32S145.648,99.312,128,99.312z
|
||||
M128,147.312H80c-8.824,0-16-7.176-16-16c0-8.824,7.176-16,16-16h48c8.824,0,16,7.176,16,16S136.824,147.312,128,147.312z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<rect x="160" y="195.312" width="16" height="16"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<rect x="64" y="195.312" width="80" height="16"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<rect x="32" y="195.312" width="16" height="16"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
@ -0,0 +1,2 @@
|
||||
<?xml version="1.0"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="512" height="512" fill="#FFFFFF"><g id="outline"><path d="M43,17A11,11,0,1,0,32,28,11.013,11.013,0,0,0,43,17ZM23,17a9,9,0,1,1,9,9A9.01,9.01,0,0,1,23,17Z"/><path d="M33,24h2V22a2.987,2.987,0,0,0,2.22-5A2.987,2.987,0,0,0,35,12V10H33v2H31V10H29v2H27v2h2v6H27v2h2v2h2V22h2Zm3-9a1,1,0,0,1-1,1H31V14h4A1,1,0,0,1,36,15Zm-5,3h4a1,1,0,0,1,0,2H31Z"/><path d="M26,52v4a1,1,0,0,0,.293.707l5,5a1,1,0,0,0,1.414,0l5-5A1,1,0,0,0,38,56V34h2a1,1,0,0,0,.707-1.707L38.74,30.326A14.815,14.815,0,0,0,47,17a15,15,0,0,0-30,0,14.815,14.815,0,0,0,8.26,13.326l-1.967,1.967A1,1,0,0,0,24,34h3v.586l-1.707,1.707A1,1,0,0,0,25,37v3a1,1,0,0,0,.629.929l3.348,1.339L26.4,44.2a1,1,0,0,0-.4.8v3a1,1,0,0,0,.445.832L28.2,50l-1.752,1.168A1,1,0,0,0,26,52Zm2,.535,2.555-1.7a1,1,0,0,0,0-1.664L28,47.465V45.5l3.6-2.7a1,1,0,0,0-.229-1.729L27,39.323V37.414l1.707-1.707A1,1,0,0,0,29,35V33a1,1,0,0,0-1-1H26.414l1.293-1.293a1,1,0,0,0-.35-1.641A12.86,12.86,0,0,1,19,17a13,13,0,0,1,26,0,12.86,12.86,0,0,1-8.357,12.066,1,1,0,0,0-.35,1.641L37.586,32H37a1,1,0,0,0-1,1V55.586l-3,3V45H31V58.586l-3-3Z"/><rect x="31" y="31" width="2" height="8"/><path d="M17,26a1,1,0,0,0-2,0,3,3,0,0,1-3,3,1,1,0,0,0,0,2,3,3,0,0,1,3,3,1,1,0,0,0,2,0,3,3,0,0,1,3-3,1,1,0,0,0,0-2A3,3,0,0,1,17,26Zm-1,5.031A4.988,4.988,0,0,0,14.969,30,4.988,4.988,0,0,0,16,28.969,4.988,4.988,0,0,0,17.031,30,4.988,4.988,0,0,0,16,31.031Z"/><path d="M41,39a3,3,0,0,1,3,3,1,1,0,0,0,2,0,3,3,0,0,1,3-3,1,1,0,0,0,0-2,3,3,0,0,1-3-3,1,1,0,0,0-2,0,3,3,0,0,1-3,3,1,1,0,0,0,0,2Zm4-2.031A4.988,4.988,0,0,0,46.031,38,4.988,4.988,0,0,0,45,39.031,4.988,4.988,0,0,0,43.969,38,4.988,4.988,0,0,0,45,36.969Z"/></g></svg>
|
After Width: | Height: | Size: 1.7 KiB |
@ -0,0 +1,16 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>RTL</title>
|
||||
<base href="/rtl/">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="assets/images/favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="assets/images/favicon/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="assets/images/favicon/favicon-16x16.png">
|
||||
<link rel="manifest" href="assets/images/favicon/site.webmanifest">
|
||||
<link rel="stylesheet" href="styles.68c5c110c36114003e31.css"></head>
|
||||
<body>
|
||||
<rtl-app></rtl-app>
|
||||
<script src="runtime.49783ae5fbe9bffd3b65.js" defer></script><script src="polyfills-es5.b8e32dec482ae69710a2.js" nomodule defer></script><script src="polyfills.ebf9033c33aa4a5af12a.js" defer></script><script src="main.0c6a645ab1638ee36ab2.js" defer></script></body>
|
||||
</html>
|
@ -0,0 +1 @@
|
||||
!function(e){function r(r){for(var n,a,i=r[0],c=r[1],l=r[2],p=0,s=[];p<i.length;p++)a=i[p],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&s.push(o[a][0]),o[a]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,l||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++)0!==o[t[i]]&&(n=!1);n&&(u.splice(r--,1),e=a(a.s=t[0]))}return e}var n={},o={0:0},u=[];function a(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,i=document.createElement("script");i.charset="utf-8",i.timeout=120,a.nc&&i.setAttribute("nonce",a.nc),i.src=function(e){return a.p+""+({}[e]||e)+"."+{1:"454a80537ee5d2127464",6:"acee564c946fe91034bb",7:"eb1dae2453682026ccc5"}[e]+".js"}(e);var c=new Error;u=function(r){i.onerror=i.onload=null,clearTimeout(l);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var l=setTimeout((function(){u({type:"timeout",target:i})}),12e4);i.onerror=i.onload=u,document.head.appendChild(i)}return Promise.all(r)},a.m=e,a.c=n,a.d=function(e,r,t){a.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,r){if(1&r&&(e=a(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)a.d(t,n,(function(r){return e[r]}).bind(null,n));return t},a.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(r,"a",r),r},a.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},a.p="",a.oe=function(e){throw console.error(e),e};var i=window.webpackJsonp=window.webpackJsonp||[],c=i.push.bind(i);i.push=r,i=i.slice();for(var l=0;l<i.length;l++)r(i[l]);var f=c;t()}([]);
|
@ -0,0 +1,438 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "google/api/annotations.proto";
|
||||
|
||||
package looprpc;
|
||||
|
||||
/**
|
||||
SwapClient is a service that handles the client side process of onchain/offchain
|
||||
swaps. The service is designed for a single client.
|
||||
*/
|
||||
service SwapClient {
|
||||
/** loop: `out`
|
||||
LoopOut initiates an loop out swap with the given parameters. The call
|
||||
returns after the swap has been set up with the swap server. From that
|
||||
point onwards, progress can be tracked via the SwapStatus stream that is
|
||||
returned from Monitor().
|
||||
*/
|
||||
rpc LoopOut (LoopOutRequest) returns (SwapResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/v1/loop/out"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
LoopIn initiates a loop in swap with the given parameters. The call
|
||||
returns after the swap has been set up with the swap server. From that
|
||||
point onwards, progress can be tracked via the SwapStatus stream
|
||||
that is returned from Monitor().
|
||||
*/
|
||||
rpc LoopIn (LoopInRequest) returns (SwapResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/v1/loop/in"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
/** loop: `monitor`
|
||||
Monitor will return a stream of swap updates for currently active swaps.
|
||||
TODO: add MonitorSync version for REST clients.
|
||||
*/
|
||||
rpc Monitor (MonitorRequest) returns (stream SwapStatus);
|
||||
|
||||
/** loop: `terms`
|
||||
LoopOutTerms returns the terms that the server enforces for a loop out swap.
|
||||
*/
|
||||
rpc LoopOutTerms (TermsRequest) returns (TermsResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/v1/loop/out/terms"
|
||||
};
|
||||
}
|
||||
|
||||
/** loop: `quote`
|
||||
LoopOutQuote returns a quote for a loop out swap with the provided
|
||||
parameters.
|
||||
*/
|
||||
rpc LoopOutQuote (QuoteRequest) returns (QuoteResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/v1/loop/out/quote/{amt}"
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
GetTerms returns the terms that the server enforces for swaps.
|
||||
*/
|
||||
rpc GetLoopInTerms (TermsRequest) returns (TermsResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/v1/loop/in/terms"
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
GetQuote returns a quote for a swap with the provided parameters.
|
||||
*/
|
||||
rpc GetLoopInQuote (QuoteRequest) returns (QuoteResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/v1/loop/in/quote/{amt}"
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
GetLsatTokens returns all LSAT tokens the daemon ever paid for.
|
||||
*/
|
||||
rpc GetLsatTokens (TokensRequest) returns (TokensResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/v1/lsat/tokens"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
message LoopOutRequest {
|
||||
/**
|
||||
Requested swap amount in sat. This does not include the swap and miner fee.
|
||||
*/
|
||||
int64 amt = 1;
|
||||
|
||||
/**
|
||||
Base58 encoded destination address for the swap.
|
||||
*/
|
||||
string dest = 2;
|
||||
|
||||
/**
|
||||
Maximum off-chain fee in msat that may be paid for payment to the server.
|
||||
This limit is applied during path finding. Typically this value is taken
|
||||
from the response of the GetQuote call.
|
||||
*/
|
||||
int64 max_swap_routing_fee = 3;
|
||||
|
||||
/**
|
||||
Maximum off-chain fee in msat that may be paid for payment to the server.
|
||||
This limit is applied during path finding. Typically this value is taken
|
||||
from the response of the GetQuote call.
|
||||
*/
|
||||
int64 max_prepay_routing_fee = 4;
|
||||
|
||||
/**
|
||||
Maximum we are willing to pay the server for the swap. This value is not
|
||||
disclosed in the swap initiation call, but if the server asks for a
|
||||
higher fee, we abort the swap. Typically this value is taken from the
|
||||
response of the GetQuote call. It includes the prepay amount.
|
||||
*/
|
||||
int64 max_swap_fee = 5;
|
||||
|
||||
/**
|
||||
Maximum amount of the swap fee that may be charged as a prepayment.
|
||||
*/
|
||||
int64 max_prepay_amt = 6;
|
||||
|
||||
/**
|
||||
Maximum in on-chain fees that we are willing to spent. If we want to
|
||||
sweep the on-chain htlc and the fee estimate turns out higher than this
|
||||
value, we cancel the swap. If the fee estimate is lower, we publish the
|
||||
sweep tx.
|
||||
If the sweep tx is not confirmed, we are forced to ratchet up fees until it
|
||||
is swept. Possibly even exceeding max_miner_fee if we get close to the htlc
|
||||
timeout. Because the initial publication revealed the preimage, we have no
|
||||
other choice. The server may already have pulled the off-chain htlc. Only
|
||||
when the fee becomes higher than the swap amount, we can only wait for fees
|
||||
to come down and hope - if we are past the timeout - that the server is not
|
||||
publishing the revocation.
|
||||
max_miner_fee is typically taken from the response of the GetQuote call.
|
||||
*/
|
||||
int64 max_miner_fee = 7;
|
||||
|
||||
/**
|
||||
The channel to loop out, the channel to loop out is selected based on the
|
||||
lowest routing fee for the swap payment to the server.
|
||||
*/
|
||||
uint64 loop_out_channel = 8;
|
||||
|
||||
/**
|
||||
The number of blocks from the on-chain HTLC's confirmation height that it
|
||||
should be swept within.
|
||||
*/
|
||||
int32 sweep_conf_target = 9;
|
||||
|
||||
/**
|
||||
The latest time (in unix seconds) we allow the server to wait before
|
||||
publishing the HTLC on chain. Setting this to a larger value will give the
|
||||
server the opportunity to batch multiple swaps together, and wait for
|
||||
low-fee periods before publishing the HTLC, potentially resulting in a
|
||||
lower total swap fee.
|
||||
*/
|
||||
uint64 swap_publication_deadline = 10;
|
||||
}
|
||||
|
||||
message LoopInRequest {
|
||||
/**
|
||||
Requested swap amount in sat. This does not include the swap and miner
|
||||
fee.
|
||||
*/
|
||||
int64 amt = 1;
|
||||
|
||||
/**
|
||||
Maximum we are willing to pay the server for the swap. This value is not
|
||||
disclosed in the swap initiation call, but if the server asks for a
|
||||
higher fee, we abort the swap. Typically this value is taken from the
|
||||
response of the GetQuote call.
|
||||
*/
|
||||
int64 max_swap_fee = 2;
|
||||
|
||||
/**
|
||||
Maximum in on-chain fees that we are willing to spent. If we want to
|
||||
publish the on-chain htlc and the fee estimate turns out higher than this
|
||||
value, we cancel the swap.
|
||||
max_miner_fee is typically taken from the response of the GetQuote call.
|
||||
*/
|
||||
int64 max_miner_fee = 3;
|
||||
|
||||
/**
|
||||
The channel to loop in. If zero, the channel to loop in is selected based
|
||||
on the lowest routing fee for the swap payment from the server.
|
||||
Note: NOT YET IMPLEMENTED
|
||||
*/
|
||||
uint64 loop_in_channel = 4;
|
||||
|
||||
/**
|
||||
If external_htlc is true, we expect the htlc to be published by an external
|
||||
actor.
|
||||
*/
|
||||
bool external_htlc = 5;
|
||||
}
|
||||
|
||||
message SwapResponse {
|
||||
/**
|
||||
Swap identifier to track status in the update stream that is returned from
|
||||
the Start() call. Currently this is the hash that locks the htlcs.
|
||||
*/
|
||||
string id = 1;
|
||||
|
||||
/**
|
||||
The address of the on-chain htlc.
|
||||
*/
|
||||
string htlc_address = 2;
|
||||
}
|
||||
|
||||
message MonitorRequest {
|
||||
}
|
||||
|
||||
message SwapStatus {
|
||||
/**
|
||||
Requested swap amount in sat. This does not include the swap and miner
|
||||
fee.
|
||||
*/
|
||||
int64 amt = 1;
|
||||
|
||||
/**
|
||||
Swap identifier to track status in the update stream that is returned from
|
||||
the Start() call. Currently this is the hash that locks the htlcs.
|
||||
*/
|
||||
string id = 2;
|
||||
|
||||
/**
|
||||
Swap type
|
||||
*/
|
||||
SwapType type = 3;
|
||||
|
||||
/**
|
||||
State the swap is currently in, see State enum.
|
||||
*/
|
||||
SwapState state = 4;
|
||||
|
||||
/**
|
||||
Initiation time of the swap.
|
||||
*/
|
||||
int64 initiation_time = 5;
|
||||
|
||||
/**
|
||||
Initiation time of the swap.
|
||||
*/
|
||||
int64 last_update_time = 6;
|
||||
|
||||
/**
|
||||
Htlc address.
|
||||
*/
|
||||
string htlc_address = 7;
|
||||
|
||||
/// Swap server cost
|
||||
int64 cost_server = 8;
|
||||
|
||||
// On-chain transaction cost
|
||||
int64 cost_onchain = 9;
|
||||
|
||||
// Off-chain routing fees
|
||||
int64 cost_offchain = 10;
|
||||
}
|
||||
|
||||
enum SwapType {
|
||||
// LOOP_OUT indicates an loop out swap (off-chain to on-chain)
|
||||
LOOP_OUT = 0;
|
||||
|
||||
// LOOP_IN indicates a loop in swap (on-chain to off-chain)
|
||||
LOOP_IN = 1;
|
||||
}
|
||||
|
||||
enum SwapState {
|
||||
/**
|
||||
INITIATED is the initial state of a swap. At that point, the initiation
|
||||
call to the server has been made and the payment process has been started
|
||||
for the swap and prepayment invoices.
|
||||
*/
|
||||
INITIATED = 0;
|
||||
|
||||
/**
|
||||
PREIMAGE_REVEALED is reached when the sweep tx publication is first
|
||||
attempted. From that point on, we should consider the preimage to no
|
||||
longer be secret and we need to do all we can to get the sweep confirmed.
|
||||
This state will mostly coalesce with StateHtlcConfirmed, except in the
|
||||
case where we wait for fees to come down before we sweep.
|
||||
*/
|
||||
PREIMAGE_REVEALED = 1;
|
||||
|
||||
/**
|
||||
HTLC_PUBLISHED is reached when the htlc tx has been published in a loop in
|
||||
swap.
|
||||
*/
|
||||
HTLC_PUBLISHED = 2;
|
||||
|
||||
/**
|
||||
SUCCESS is the final swap state that is reached when the sweep tx has
|
||||
the required confirmation depth.
|
||||
*/
|
||||
SUCCESS = 3;
|
||||
|
||||
/**
|
||||
FAILED is the final swap state for a failed swap with or without loss of
|
||||
the swap amount.
|
||||
*/
|
||||
FAILED = 4;
|
||||
|
||||
/**
|
||||
INVOICE_SETTLED is reached when the swap invoice in a loop in swap has been
|
||||
paid, but we are still waiting for the htlc spend to confirm.
|
||||
*/
|
||||
INVOICE_SETTLED = 5;
|
||||
}
|
||||
|
||||
message TermsRequest {
|
||||
}
|
||||
|
||||
message TermsResponse {
|
||||
|
||||
reserved 1, 2, 3, 4, 7;
|
||||
|
||||
/**
|
||||
Minimum swap amount (sat)
|
||||
*/
|
||||
int64 min_swap_amount = 5;
|
||||
|
||||
/**
|
||||
Maximum swap amount (sat)
|
||||
*/
|
||||
int64 max_swap_amount = 6;
|
||||
}
|
||||
|
||||
message QuoteRequest {
|
||||
/**
|
||||
The amount to swap in satoshis.
|
||||
*/
|
||||
int64 amt = 1;
|
||||
|
||||
/**
|
||||
The confirmation target that should be used either for the sweep of the
|
||||
on-chain HTLC broadcast by the swap server in the case of a Loop Out, or for
|
||||
the confirmation of the on-chain HTLC broadcast by the swap client in the
|
||||
case of a Loop In.
|
||||
*/
|
||||
int32 conf_target = 2;
|
||||
|
||||
/**
|
||||
If external_htlc is true, we expect the htlc to be published by an external
|
||||
actor.
|
||||
*/
|
||||
bool external_htlc = 3;
|
||||
}
|
||||
|
||||
message QuoteResponse {
|
||||
/**
|
||||
The fee that the swap server is charging for the swap.
|
||||
*/
|
||||
int64 swap_fee = 1;
|
||||
|
||||
/**
|
||||
The part of the swap fee that is requested as a prepayment.
|
||||
*/
|
||||
int64 prepay_amt = 2;
|
||||
|
||||
/**
|
||||
An estimate of the on-chain fee that needs to be paid to sweep the HTLC.
|
||||
*/
|
||||
int64 miner_fee = 3;
|
||||
|
||||
/**
|
||||
The node pubkey where the swap payment needs to be paid
|
||||
to. This can be used to test connectivity before initiating the swap.
|
||||
*/
|
||||
bytes swap_payment_dest = 4;
|
||||
|
||||
/**
|
||||
On-chain cltv expiry delta
|
||||
*/
|
||||
int32 cltv_delta = 5;
|
||||
}
|
||||
|
||||
message TokensRequest {
|
||||
}
|
||||
|
||||
message TokensResponse {
|
||||
/**
|
||||
List of all tokens the daemon knows of, including old/expired tokens.
|
||||
*/
|
||||
repeated LsatToken tokens = 1;
|
||||
}
|
||||
|
||||
message LsatToken {
|
||||
/**
|
||||
The base macaroon that was baked by the auth server.
|
||||
*/
|
||||
bytes base_macaroon = 1;
|
||||
|
||||
/**
|
||||
The payment hash of the payment that was paid to obtain the token.
|
||||
*/
|
||||
bytes payment_hash = 2;
|
||||
|
||||
/**
|
||||
The preimage of the payment hash, knowledge of this is proof that the
|
||||
payment has been paid. If the preimage is set to all zeros, this means the
|
||||
payment is still pending and the token is not yet fully valid.
|
||||
*/
|
||||
bytes payment_preimage = 3;
|
||||
|
||||
/**
|
||||
The amount of millisatoshis that was paid to get the token.
|
||||
*/
|
||||
int64 amount_paid_msat = 4;
|
||||
|
||||
/**
|
||||
The amount of millisatoshis paid in routing fee to pay for the token.
|
||||
*/
|
||||
int64 routing_fee_paid_msat = 5;
|
||||
|
||||
/**
|
||||
The creation time of the token as UNIX timestamp in seconds.
|
||||
*/
|
||||
int64 time_created = 6;
|
||||
|
||||
/**
|
||||
Indicates whether the token is expired or still valid.
|
||||
*/
|
||||
bool expired = 7;
|
||||
|
||||
/**
|
||||
Identifying attribute of this token in the store. Currently represents the
|
||||
file name of the token where it's stored on the file system.
|
||||
*/
|
||||
string storage_name = 8;
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
const grpc = require('grpc');
|
||||
const protoLoader = require('@grpc/proto-loader');
|
||||
const packageDefinition = protoLoader.loadSync('./controllers/loopd/client.proto', {keepCase: true, longs: String, enums: String, defaults: true, oneofs: true});
|
||||
const looprpc = grpc.loadPackageDefinition(packageDefinition).looprpc;
|
||||
const swapClient = new looprpc.SwapClient('localhost:11010', grpc.credentials.createInsecure());
|
||||
|
||||
exports.loopIn = (req, res, next) => {
|
||||
swapClient.loopIn(req.body, function(error, response) {
|
||||
if (error) {
|
||||
res.status(500).json({message: 'Loop In Failed!', error: error});
|
||||
} else {
|
||||
res.status(200).json(response);
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
exports.loopOut = (req, res, next) => {
|
||||
swapClient.loopOut(req.body, function(error, response) {
|
||||
if (error) {
|
||||
res.status(500).json({message: 'Loop Out Failed!', error: error});
|
||||
} else {
|
||||
res.status(200).json(response);
|
||||
}
|
||||
})
|
||||
};
|
@ -0,0 +1,9 @@
|
||||
const LoopController = require("../../controllers/loopd/loop");
|
||||
const express = require("express");
|
||||
const router = express.Router();
|
||||
const authCheck = require("../authCheck");
|
||||
|
||||
router.get("/loopin", authCheck, LoopController.loopIn);
|
||||
router.get("/loopout", authCheck, LoopController.loopOut);
|
||||
|
||||
module.exports = router;
|
@ -1,4 +1,4 @@
|
||||
.inline-spinner {
|
||||
display: inline-flex !important;
|
||||
top: 0px !important;
|
||||
top: 0 !important;
|
||||
}
|
||||
|
@ -1,136 +0,0 @@
|
||||
<div fxLayout="column">
|
||||
<div class="padding-gap">
|
||||
<mat-card [ngClass]="{'flip': redirectedWithPeer}">
|
||||
<mat-card-header>
|
||||
<mat-card-subtitle>
|
||||
<h2>Open Channel</h2>
|
||||
</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<form fxLayout="column" fxLayout.gt-sm="row wrap"
|
||||
(ngSubmit)="openChannelForm.form.valid && onOpenChannel(openChannelForm)" #openChannelForm="ngForm">
|
||||
<mat-form-field fxFlex="40" fxLayoutAlign="start end">
|
||||
<mat-select [(ngModel)]="selectedPeer" placeholder="Alias" name="peer_alias" tabindex="1" required
|
||||
name="selPeer" #selPeer="ngModel">
|
||||
<mat-option *ngFor="let peer of peers" [value]="peer.id">
|
||||
{{peer.alias}}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field fxFlex="25" fxLayoutAlign="start end">
|
||||
<input matInput [(ngModel)]="fundingAmount" placeholder="Amount (mSats)" type="number" step="1000" min="1"
|
||||
tabindex="2" required name="amount" #amount="ngModel">
|
||||
<mat-hint>(Wallet Bal: {{totalBalance}}, Remaining Bal:
|
||||
{{totalBalance - ((fundingAmount) ? fundingAmount : 0)}})</mat-hint>
|
||||
</mat-form-field>
|
||||
<div fxFlex="15" tabindex="3" fxLayoutAlign="start center" class="chkbox-options">
|
||||
<mat-checkbox [(ngModel)]="moreOptions" name="moreOptions" (change)="onMoreOptionsChange($event)">Options
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
<span *ngIf="moreOptions" fxLayout="column" fxLayout.gt-sm="row wrap" fxFlex="80"
|
||||
fxLayoutAlign.gt-sm="start center">
|
||||
<mat-form-field fxFlex="25" fxLayoutAlign="start end">
|
||||
<mat-select tabindex="4" placeholder="Fee Rate" [(value)]="selFeeRate">
|
||||
<mat-option *ngFor="let feeRateType of feeRateTypes" [value]="feeRateType.feeRateId">
|
||||
{{feeRateType.feeRateType}}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-checkbox fxFlex="25" fxFlex.lt-lg="35" tabindex="5" [(ngModel)]="flgMinConf" name="flgMinConf">
|
||||
<mat-form-field fxFlex="100">
|
||||
<input matInput [(ngModel)]="minConfValue" placeholder="Min Confirmation Blocks" type="number"
|
||||
name="blocks" step="1" min="0" tabindex="6" #blocks="ngModel" [required]="flgMinConf"
|
||||
[disabled]="!flgMinConf">
|
||||
</mat-form-field>
|
||||
</mat-checkbox>
|
||||
</span>
|
||||
<div fxFlex="10" fxLayoutAlign="end start">
|
||||
<button fxFlex="90" fxLayoutAlign="center center" mat-raised-button color="primary"
|
||||
[disabled]="selectedPeer === '' || fundingAmount == null || (totalBalance - ((fundingAmount) ? fundingAmount : 0) < 0) || !openChannelForm.valid"
|
||||
type="submit" tabindex="7">
|
||||
<p
|
||||
*ngIf="(selectedPeer === '' || fundingAmount == null) && (selPeer.touched || selPeer.dirty) && (amount.touched || amount.dirty); else openText">
|
||||
Invalid Values</p>
|
||||
<ng-template #openText>
|
||||
<p>Open</p>
|
||||
</ng-template>
|
||||
</button>
|
||||
</div>
|
||||
<div fxFlex="10" fxLayoutAlign="end start">
|
||||
<button fxFlex="90" fxLayoutAlign="center center" mat-raised-button color="accent" tabindex="9" type="reset"
|
||||
(click)="resetData()">Clear</button>
|
||||
</div>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
<div class="padding-gap">
|
||||
<mat-card>
|
||||
<mat-card-content fxFlex="100" fxLayout="column">
|
||||
<div fxLayout="row" fxLayoutAlign="start start">
|
||||
<mat-form-field fxFlex="30">
|
||||
<input matInput (keyup)="applyFilter()" [(ngModel)]="selFilter" name="filter" placeholder="Filter">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div perfectScrollbar class="table-container mat-elevation-z8">
|
||||
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
|
||||
<table mat-table #table [dataSource]="channels" matSort
|
||||
[ngClass]="{'mat-elevation-z8 overflow-auto error-border': flgLoading[0]==='error','mat-elevation-z8 overflow-auto': true}">
|
||||
<ng-container matColumnDef="close">
|
||||
<th mat-header-cell *matHeaderCellDef> Close Channel </th>
|
||||
<td mat-cell *matCellDef="let channel">
|
||||
<mat-icon color="accent" (click)="onChannelClose(channel)">link_off</mat-icon>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="update">
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
<mat-icon color="accent" (click)="onChannelUpdate('all')">edit</mat-icon>
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let channel">
|
||||
<mat-icon color="accent" (click)="onChannelUpdate(channel)">edit</mat-icon>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="short_channel_id">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Short Channel ID </th>
|
||||
<td mat-cell *matCellDef="let channel"> {{channel?.short_channel_id}}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="alias">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Alias </th>
|
||||
<td mat-cell *matCellDef="let channel">{{channel?.alias}}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="connected">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Connected </th>
|
||||
<td mat-cell *matCellDef="let channel"> {{(channel?.connected) ? 'Connected' : 'Disconnected'}} </td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="private">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Private </th>
|
||||
<td mat-cell *matCellDef="let channel"> {{(channel?.private ? 'Private' : 'Public')}} </td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="state">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> State </th>
|
||||
<td mat-cell *matCellDef="let channel"> {{channel?.state}}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="msatoshi_to_us">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> mSatoshi To Us </th>
|
||||
<td mat-cell *matCellDef="let channel"><span fxLayoutAlign="end center">
|
||||
{{channel?.msatoshi_to_us | number}} </span></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="msatoshi_total">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> mSatoshi Total </th>
|
||||
<td mat-cell *matCellDef="let channel"><span fxLayoutAlign="end center">
|
||||
{{channel?.msatoshi_total | number}} </span></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="spendable_msatoshi">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Spendable Satoshi </th>
|
||||
<td mat-cell *matCellDef="let channel"><span fxLayoutAlign="end center">
|
||||
{{channel?.spendable_msatoshi | number}} </span></td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: flgSticky;"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;" (click)="onChannelClick(row, $event)"
|
||||
[ngClass]="{'row-disabled': row.state === 'ONCHAIN'}"></tr>
|
||||
</table>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
@ -1,271 +0,0 @@
|
||||
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import { MatTableDataSource, MatSort } from '@angular/material';
|
||||
import { ChannelCL, PeerCL, GetInfoCL, ChannelEdgeCL } from '../../shared/models/clModels';
|
||||
import { LoggerService } from '../../shared/services/logger.service';
|
||||
|
||||
import { CLEffects } from '../store/cl.effects';
|
||||
import { RTLEffects } from '../../store/rtl.effects';
|
||||
import * as RTLActions from '../../store/rtl.actions';
|
||||
import * as fromRTLReducer from '../../store/rtl.reducers';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-cl-channels',
|
||||
templateUrl: './channels.component.html',
|
||||
styleUrls: ['./channels.component.scss']
|
||||
})
|
||||
export class CLChannelsComponent implements OnInit, OnDestroy {
|
||||
@ViewChild(MatSort, { static: true }) sort: MatSort;
|
||||
public totalBalance = 0;
|
||||
public selectedPeer = '';
|
||||
public fundingAmount: number;
|
||||
public displayedColumns = [];
|
||||
public channels: any;
|
||||
public peers: PeerCL[] = [];
|
||||
public information: GetInfoCL = {};
|
||||
public flgLoading: Array<Boolean | 'error'> = [true];
|
||||
public selectedFilter = '';
|
||||
public myChanPolicy: any = {};
|
||||
public selFilter = '';
|
||||
public feeRateTypes = [];
|
||||
public selFeeRate = '';
|
||||
public isPrivate = false;
|
||||
public flgMinConf = false;
|
||||
public minConfValue = null;
|
||||
public moreOptions = false;
|
||||
public flgSticky = false;
|
||||
public redirectedWithPeer = false;
|
||||
private unsub: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject()];
|
||||
|
||||
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>, private rtlEffects: RTLEffects, private clEffects: CLEffects, private activatedRoute: ActivatedRoute) {
|
||||
switch (true) {
|
||||
case (window.innerWidth <= 415):
|
||||
this.displayedColumns = ['close', 'update', 'short_channel_id', 'state', 'msatoshi_total'];
|
||||
break;
|
||||
case (window.innerWidth > 415 && window.innerWidth <= 730):
|
||||
this.displayedColumns = ['close', 'update', 'short_channel_id', 'state', 'msatoshi_to_us', 'msatoshi_total', 'spendable_msatoshi'];
|
||||
break;
|
||||
case (window.innerWidth > 730 && window.innerWidth <= 1024):
|
||||
this.displayedColumns = [
|
||||
'close', 'update', 'short_channel_id', 'alias', 'connected', 'private', 'state', 'msatoshi_to_us', 'msatoshi_total', 'spendable_msatoshi'
|
||||
];
|
||||
break;
|
||||
case (window.innerWidth > 1024 && window.innerWidth <= 1280):
|
||||
this.flgSticky = true;
|
||||
this.displayedColumns = [
|
||||
'close', 'update', 'short_channel_id', 'alias', 'connected', 'private', 'state', 'msatoshi_to_us', 'msatoshi_total', 'spendable_msatoshi'
|
||||
];
|
||||
break;
|
||||
default:
|
||||
this.flgSticky = true;
|
||||
this.displayedColumns = [
|
||||
'close', 'update', 'short_channel_id', 'alias', 'connected', 'private', 'state', 'msatoshi_to_us', 'msatoshi_total', 'spendable_msatoshi'
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.store.dispatch(new RTLActions.FetchChannelsCL());
|
||||
this.store.select('cl')
|
||||
.pipe(takeUntil(this.unsub[0]))
|
||||
.subscribe((rtlStore) => {
|
||||
rtlStore.effectErrorsCl.forEach(effectsErr => {
|
||||
if (effectsErr.action === 'FetchChannelsCL') {
|
||||
this.flgLoading[0] = 'error';
|
||||
}
|
||||
});
|
||||
this.information = rtlStore.information;
|
||||
this.feeRateTypes = rtlStore.feeRateTypes;
|
||||
this.peers = rtlStore.peers;
|
||||
this.peers.forEach(peer => {
|
||||
if (undefined === peer.alias || peer.alias === '') {
|
||||
peer.alias = peer.id.substring(0, 15) + '...';
|
||||
}
|
||||
});
|
||||
|
||||
this.totalBalance = +rtlStore.balance.totalBalance;
|
||||
if (undefined !== rtlStore.allChannels) {
|
||||
this.loadChannelsTable(rtlStore.allChannels);
|
||||
}
|
||||
if (this.flgLoading[0] !== 'error') {
|
||||
this.flgLoading[0] = (undefined !== rtlStore.allChannels) ? false : true;
|
||||
}
|
||||
this.logger.info(rtlStore);
|
||||
});
|
||||
this.activatedRoute.paramMap.subscribe(() => {
|
||||
this.selectedPeer = window.history.state.peer;
|
||||
this.redirectedWithPeer = (window.history.state.peer) ? true : false;
|
||||
});
|
||||
}
|
||||
|
||||
onOpenChannel(form: any) {
|
||||
this.store.dispatch(new RTLActions.OpenSpinner('Opening Channel...'));
|
||||
this.store.dispatch(new RTLActions.SaveNewChannelCL({
|
||||
peerId: this.selectedPeer, satoshis: this.fundingAmount, announce: !this.isPrivate, feeRate: this.selFeeRate, minconf: this.flgMinConf ? this.minConfValue : null
|
||||
}));
|
||||
}
|
||||
|
||||
onChannelUpdate(channelToUpdate: ChannelCL | 'all') {
|
||||
if(channelToUpdate !== 'all' && channelToUpdate.state === 'ONCHAIN') {
|
||||
return;
|
||||
}
|
||||
if (channelToUpdate === 'all') {
|
||||
const titleMsg = 'Updated Values for ALL Channels';
|
||||
const confirmationMsg = {};
|
||||
this.store.dispatch(new RTLActions.OpenConfirmation({ width: '70%', data: {
|
||||
type: 'CONFIRM', titleMessage: titleMsg, noBtnText: 'Cancel', yesBtnText: 'Update', message: JSON.stringify(confirmationMsg), flgShowInput: true, getInputs: [
|
||||
{placeholder: 'Base Fee msat', inputType: 'number', inputValue: 1000},
|
||||
{placeholder: 'Fee Rate mili msat', inputType: 'number', inputValue: 1, min: 1}
|
||||
]
|
||||
}}));
|
||||
this.rtlEffects.closeConfirm
|
||||
.pipe(takeUntil(this.unsub[2]))
|
||||
.subscribe(confirmRes => {
|
||||
if (confirmRes) {
|
||||
const base_fee = confirmRes[0].inputValue;
|
||||
const fee_rate = confirmRes[1].inputValue;
|
||||
this.store.dispatch(new RTLActions.OpenSpinner('Updating Channel Policy...'));
|
||||
this.store.dispatch(new RTLActions.UpdateChannelsCL({baseFeeMsat: base_fee, feeRate: fee_rate, channelId: 'all'}));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.myChanPolicy = {fee_base_msat: 0, fee_rate_milli_msat: 0};
|
||||
this.store.dispatch(new RTLActions.OpenSpinner('Fetching Channel Policy...'));
|
||||
this.store.dispatch(new RTLActions.ChannelLookupCL(channelToUpdate.short_channel_id));
|
||||
this.clEffects.setLookupCL
|
||||
.pipe(takeUntil(this.unsub[3]))
|
||||
.subscribe((resLookup: ChannelEdgeCL[]) => {
|
||||
this.logger.info(resLookup);
|
||||
if (resLookup.length > 0 && resLookup[0].source === this.information.id) {
|
||||
this.myChanPolicy = {fee_base_msat: resLookup[0].base_fee_millisatoshi, fee_rate_milli_msat: resLookup[0].fee_per_millionth};
|
||||
} else if (resLookup.length > 1 && resLookup[1].source === this.information.id) {
|
||||
this.myChanPolicy = {fee_base_msat: resLookup[1].base_fee_millisatoshi, fee_rate_milli_msat: resLookup[1].fee_per_millionth};
|
||||
} else {
|
||||
this.myChanPolicy = {fee_base_msat: 0, fee_rate_milli_msat: 0};
|
||||
}
|
||||
this.logger.info(this.myChanPolicy);
|
||||
this.store.dispatch(new RTLActions.CloseSpinner());
|
||||
const titleMsg = 'Updated Values for Channel: ' + channelToUpdate.channel_id;
|
||||
const confirmationMsg = {};
|
||||
this.store.dispatch(new RTLActions.OpenConfirmation({ width: '70%', data: {
|
||||
type: 'CONFIRM', titleMessage: titleMsg, noBtnText: 'Cancel', yesBtnText: 'Update', message: JSON.stringify(confirmationMsg), flgShowInput: true, getInputs: [
|
||||
{placeholder: 'Base Fee msat', inputType: 'number', inputValue: (this.myChanPolicy.fee_base_msat === '') ? 0 : this.myChanPolicy.fee_base_msat},
|
||||
{placeholder: 'Fee Rate mili msat', inputType: 'number', inputValue: this.myChanPolicy.fee_rate_milli_msat, min: 1}
|
||||
]
|
||||
}}));
|
||||
});
|
||||
this.rtlEffects.closeConfirm
|
||||
.pipe(takeUntil(this.unsub[2]))
|
||||
.subscribe(confirmRes => {
|
||||
if (confirmRes) {
|
||||
const base_fee = confirmRes[0].inputValue;
|
||||
const fee_rate = confirmRes[1].inputValue;
|
||||
this.store.dispatch(new RTLActions.OpenSpinner('Updating Channel Policy...'));
|
||||
this.store.dispatch(new RTLActions.UpdateChannelsCL({baseFeeMsat: base_fee, feeRate: fee_rate, channelId: channelToUpdate.channel_id}));
|
||||
}
|
||||
});
|
||||
}
|
||||
this.applyFilter();
|
||||
}
|
||||
|
||||
onChannelClose(channelToClose: ChannelCL) {
|
||||
if(channelToClose.state === 'ONCHAIN') {
|
||||
return;
|
||||
}
|
||||
if (channelToClose.state === 'AWAITING_UNILATERAL') {
|
||||
this.store.dispatch(new RTLActions.OpenAlert({ width: '75%', data: {
|
||||
type: 'WARN',
|
||||
titleMessage: 'Channel can not be closed when it is in AWAITING UNILATERAL state.'
|
||||
}}));
|
||||
} else {
|
||||
this.store.dispatch(new RTLActions.OpenConfirmation({
|
||||
width: '70%', data: { type: 'CONFIRM', titleMessage: 'Closing channel: ' + channelToClose.channel_id, noBtnText: 'Cancel', yesBtnText: 'Close Channel'
|
||||
}}));
|
||||
this.rtlEffects.closeConfirm
|
||||
.pipe(takeUntil(this.unsub[1]))
|
||||
.subscribe(confirmRes => {
|
||||
if (confirmRes) {
|
||||
this.store.dispatch(new RTLActions.OpenSpinner('Closing Channel...'));
|
||||
this.store.dispatch(new RTLActions.CloseChannelCL({channelId: channelToClose.channel_id}));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
applyFilter() {
|
||||
this.selectedFilter = this.selFilter;
|
||||
this.channels.filter = this.selFilter;
|
||||
}
|
||||
|
||||
onChannelClick(selRow: ChannelCL, event: any) {
|
||||
const flgCloseClicked =
|
||||
event.target.className.includes('mat-column-close')
|
||||
|| event.target.className.includes('mat-column-update')
|
||||
|| event.target.className.includes('mat-icon');
|
||||
if (flgCloseClicked) {
|
||||
return;
|
||||
}
|
||||
const selChannel = this.channels.data.filter(channel => {
|
||||
return channel.channel_id === selRow.channel_id;
|
||||
})[0];
|
||||
const reorderedChannel = JSON.parse(JSON.stringify(selChannel, [
|
||||
'channel_id', 'short_channel_id', 'id', 'alias', 'connected', 'private', 'state', 'funding_txid', 'msatoshi_to_us', 'msatoshi_total', 'their_channel_reserve_satoshis', 'our_channel_reserve_satoshis', 'spendable_msatoshi'
|
||||
] , 2));
|
||||
this.store.dispatch(new RTLActions.OpenAlert({ width: '75%', data: {
|
||||
type: 'INFO',
|
||||
message: JSON.stringify(reorderedChannel)
|
||||
}}));
|
||||
}
|
||||
|
||||
loadChannelsTable(channels) {
|
||||
channels.sort(function(a, b) {
|
||||
return (a && a.active === b.active) ? 0 : ((b.active) ? 1 : -1);
|
||||
});
|
||||
this.channels = new MatTableDataSource<ChannelCL>([...channels]);
|
||||
this.channels.sort = this.sort;
|
||||
this.channels.filterPredicate = (channel: ChannelCL, fltr: string) => {
|
||||
const newChannel = ((channel.connected) ? 'connected' : 'disconnected') + (channel.channel_id ? channel.channel_id : '') +
|
||||
(channel.short_channel_id ? channel.short_channel_id : '') + (channel.id ? channel.id : '') + (channel.alias ? channel.alias : '') +
|
||||
(channel.private ? 'private' : 'public') + (channel.state ? channel.state.toLowerCase() : '') +
|
||||
(channel.funding_txid ? channel.funding_txid : '') + (channel.msatoshi_to_us ? channel.msatoshi_to_us : '') +
|
||||
(channel.msatoshi_total ? channel.msatoshi_total : '') + (channel.their_channel_reserve_satoshis ? channel.their_channel_reserve_satoshis : '') +
|
||||
(channel.our_channel_reserve_satoshis ? channel.our_channel_reserve_satoshis : '') + (channel.spendable_msatoshi ? channel.spendable_msatoshi : '');
|
||||
return newChannel.includes(fltr.toLowerCase());
|
||||
};
|
||||
this.logger.info(this.channels);
|
||||
}
|
||||
|
||||
resetData() {
|
||||
this.selectedPeer = '';
|
||||
this.fundingAmount = 0;
|
||||
this.moreOptions = false;
|
||||
this.flgMinConf = false;
|
||||
this.isPrivate = false;
|
||||
this.selFeeRate = '';
|
||||
this.minConfValue = null;
|
||||
this.redirectedWithPeer = false;
|
||||
}
|
||||
|
||||
onMoreOptionsChange(event: any) {
|
||||
if (!event.checked) {
|
||||
this.flgMinConf = false;
|
||||
this.isPrivate = false;
|
||||
this.selFeeRate = '';
|
||||
this.minConfValue = null;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.unsub.forEach(completeSub => {
|
||||
completeSub.next();
|
||||
completeSub.complete();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -1 +1,2 @@
|
||||
<router-outlet></router-outlet>
|
||||
<mat-progress-bar *ngIf="loading" color="primary" mode="indeterminate"></mat-progress-bar>
|
||||
<router-outlet *ngIf="!loading"></router-outlet>
|
||||
|
@ -1,95 +0,0 @@
|
||||
<div fxLayout="column">
|
||||
<!-- <div fxFlex="100" class="padding-gap">
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-subtitle>
|
||||
<h2>Forwarding History</h2>
|
||||
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
|
||||
</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<form fxLayout="column" fxLayoutAlign="space-between stretch" fxLayout.gt-md="row wrap"
|
||||
(ngSubmit)="fhForm.form.valid && onForwardingHistoryFetch()" #fhForm="ngForm" class="padding-gap">
|
||||
<div fxFlex="69" fxLayoutAlign="space-between stretch">
|
||||
<mat-form-field fxFlex="49" fxLayoutAlign="start">
|
||||
<input matInput [matDatepicker]="startDatepicker" placeholder="Start Date" [max]="yesterday"
|
||||
name="startDate" [(ngModel)]="startDate" tabindex="1" #strtDate="ngModel">
|
||||
<mat-datepicker-toggle matSuffix [for]="startDatepicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #startDatepicker [startAt]="startDate"></mat-datepicker>
|
||||
</mat-form-field>
|
||||
<mat-form-field fxFlex="49" fxLayoutAlign="start">
|
||||
<input matInput [matDatepicker]="endDatepicker" [max]="today" placeholder="End Date" name="endDate"
|
||||
[(ngModel)]="endDate" tabindex="2" #enDate="ngModel">
|
||||
<mat-datepicker-toggle matSuffix [for]="endDatepicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #endDatepicker [startAt]="endDate"></mat-datepicker>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div fxFlex="30" fxLayoutAlign="space-between stretch">
|
||||
<button fxFlex="50" fxLayoutAlign="center center" mat-raised-button color="primary"
|
||||
[disabled]="fhForm.invalid" type="submit" tabindex="3">Fetch</button>
|
||||
<button fxFlex="50" fxLayoutAlign="center center" mat-raised-button color="accent" class="ml-2" tabindex="4"
|
||||
type="reset" (click)="resetData()">Clear</button>
|
||||
</div>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div> -->
|
||||
<div class="padding-gap">
|
||||
<mat-card>
|
||||
<mat-card-content class="table-card-content">
|
||||
<div fxLayout="row" fxLayoutAlign="start start">
|
||||
<mat-form-field fxFlex="30">
|
||||
<input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div perfectScrollbar class="table-container mat-elevation-z8">
|
||||
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
|
||||
<table mat-table #table [dataSource]="forwardingHistoryEvents" matSort
|
||||
[ngClass]="{'mat-elevation-z8 overflow-auto error-border': flgLoading[0]==='error','mat-elevation-z8 overflow-auto': true}">
|
||||
<ng-container matColumnDef="status">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Status</th>
|
||||
<td mat-cell *matCellDef="let fhEvent">{{fhEvent?.status}}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="received_time_str">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Received Time</th>
|
||||
<td mat-cell *matCellDef="let fhEvent">{{fhEvent?.received_time_str}}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="resolved_time_str">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Resolved Time</th>
|
||||
<td mat-cell *matCellDef="let fhEvent">{{fhEvent?.resolved_time_str}}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="in_channel">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>In Channel</th>
|
||||
<td mat-cell *matCellDef="let fhEvent">{{fhEvent?.in_channel}}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="out_channel">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Out Channel</th>
|
||||
<td mat-cell *matCellDef="let fhEvent">{{fhEvent?.out_channel}}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="in_msatoshi">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">In mSatoshi</th>
|
||||
<td mat-cell *matCellDef="let fhEvent"><span fxLayoutAlign="end center">{{fhEvent?.in_msatoshi | number}}</span></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="out_msatoshi">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Out mSatoshi</th>
|
||||
<td mat-cell *matCellDef="let fhEvent"><span fxLayoutAlign="end center">{{fhEvent?.out_msatoshi | number}}</span></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="fee">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Fee mSat</th>
|
||||
<td mat-cell *matCellDef="let fhEvent"><span fxLayoutAlign="end center">{{fhEvent?.fee | number}}</span></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="payment_hash">
|
||||
<th mat-header-cell class="pl-4" *matHeaderCellDef mat-sort-header>Payment Hash</th>
|
||||
<td mat-cell class="pl-4" *matCellDef="let fhEvent">
|
||||
<div>{{fhEvent?.payment_hash | slice:0:10}}...</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: flgSticky;"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"
|
||||
(click)="onForwardingEventClick(row, $event)"></tr>
|
||||
</table>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
@ -1,3 +0,0 @@
|
||||
table {
|
||||
width:100%;
|
||||
}
|
@ -1,136 +0,0 @@
|
||||
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { formatDate } from '@angular/common';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil, filter } from 'rxjs/operators';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Actions } from '@ngrx/effects';
|
||||
|
||||
import { MatTableDataSource, MatSort } from '@angular/material';
|
||||
import { ForwardingEventCL } from '../../shared/models/clModels';
|
||||
import { LoggerService } from '../../shared/services/logger.service';
|
||||
|
||||
import * as RTLActions from '../../store/rtl.actions';
|
||||
import * as fromRTLReducer from '../../store/rtl.reducers';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-cl-forwarding-history',
|
||||
templateUrl: './forwarding-history.component.html',
|
||||
styleUrls: ['./forwarding-history.component.scss']
|
||||
})
|
||||
export class CLForwardingHistoryComponent implements OnInit, OnDestroy {
|
||||
@ViewChild(MatSort, { static: true }) sort: MatSort;
|
||||
public displayedColumns = [];
|
||||
public forwardingHistoryEvents: any;
|
||||
public lastOffsetIndex = 0;
|
||||
public flgLoading: Array<Boolean | 'error'> = [true];
|
||||
public today = new Date(Date.now());
|
||||
public yesterday = new Date(this.today.getFullYear(), this.today.getMonth(), this.today.getDate() - 1, this.today.getHours(), this.today.getMinutes(), this.today.getSeconds());
|
||||
public endDate = this.today;
|
||||
public startDate = this.yesterday;
|
||||
public flgSticky = false;
|
||||
private unsub: Array<Subject<void>> = [new Subject(), new Subject(), new Subject()];
|
||||
|
||||
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>, private actions$: Actions) {
|
||||
switch (true) {
|
||||
case (window.innerWidth <= 415):
|
||||
this.displayedColumns = ['status', 'in_msatoshi', 'out_msatoshi'];
|
||||
break;
|
||||
case (window.innerWidth > 415 && window.innerWidth <= 730):
|
||||
this.displayedColumns = ['status', 'in_msatoshi', 'out_msatoshi', 'fee'];
|
||||
break;
|
||||
case (window.innerWidth > 730 && window.innerWidth <= 1024):
|
||||
this.displayedColumns = ['status', 'received_time_str', 'resolved_time_str', 'in_channel', 'out_channel', 'in_msatoshi', 'out_msatoshi', 'fee', 'payment_hash'];
|
||||
break;
|
||||
case (window.innerWidth > 1024 && window.innerWidth <= 1280):
|
||||
this.flgSticky = true;
|
||||
this.displayedColumns = ['status', 'received_time_str', 'resolved_time_str', 'in_channel', 'out_channel', 'in_msatoshi', 'out_msatoshi', 'fee', 'payment_hash'];
|
||||
break;
|
||||
default:
|
||||
this.flgSticky = true;
|
||||
this.displayedColumns = ['status', 'received_time_str', 'resolved_time_str', 'in_channel', 'out_channel', 'in_msatoshi', 'out_msatoshi', 'fee', 'payment_hash'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.onForwardingHistoryFetchCL();
|
||||
this.store.select('cl')
|
||||
.pipe(takeUntil(this.unsub[0]))
|
||||
.subscribe((rtlStore) => {
|
||||
rtlStore.effectErrorsCl.forEach(effectsErr => {
|
||||
if (effectsErr.action === 'GetForwardingHistoryCL') {
|
||||
this.flgLoading[0] = 'error';
|
||||
}
|
||||
});
|
||||
if (undefined !== rtlStore.forwardingHistory.forwarding_events && rtlStore.forwardingHistory.forwarding_events.length > 0) {
|
||||
this.lastOffsetIndex = rtlStore.forwardingHistory.last_offset_index;
|
||||
this.loadForwardingEventsTable(rtlStore.forwardingHistory.forwarding_events);
|
||||
} else {
|
||||
// To reset table after other Forwarding history calls
|
||||
this.lastOffsetIndex = 0;
|
||||
this.loadForwardingEventsTable([]);
|
||||
}
|
||||
if (this.flgLoading[0] !== 'error') {
|
||||
this.flgLoading[0] = (undefined !== rtlStore.forwardingHistory.forwarding_events) ? false : true;
|
||||
}
|
||||
this.logger.info(rtlStore);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
onForwardingEventClick(selRow: ForwardingEventCL, event: any) {
|
||||
const selFEvent = this.forwardingHistoryEvents.data.filter(fhEvent => {
|
||||
return (fhEvent.received_time === selRow.received_time && fhEvent.in_channel === selRow.in_channel);
|
||||
})[0];
|
||||
const reorderedFHEvent = JSON.parse(JSON.stringify(selFEvent, [
|
||||
'status', 'received_time_str', 'resolved_time_str', 'in_channel', 'out_channel', 'in_msatoshi', 'in_msat', 'out_msatoshi', 'out_msat', 'fee', 'fee_msat', 'payment_hash'
|
||||
] , 2));
|
||||
this.store.dispatch(new RTLActions.OpenAlert({ width: '75%', data: {
|
||||
type: 'INFO',
|
||||
message: JSON.stringify(reorderedFHEvent)
|
||||
}}));
|
||||
}
|
||||
|
||||
loadForwardingEventsTable(forwardingEvents: ForwardingEventCL[]) {
|
||||
this.forwardingHistoryEvents = new MatTableDataSource<ForwardingEventCL>([...forwardingEvents]);
|
||||
this.forwardingHistoryEvents.sort = this.sort;
|
||||
this.forwardingHistoryEvents.data.forEach(event => {
|
||||
event.received_time_str = (event.received_time_str === '') ? '' : formatDate(event.received_time_str, 'MMM/dd/yy HH:mm:ss', 'en-US');
|
||||
event.resolved_time_str = (event.resolved_time_str === '') ? '' : formatDate(event.resolved_time_str, 'MMM/dd/yy HH:mm:ss', 'en-US');
|
||||
});
|
||||
this.logger.info(this.forwardingHistoryEvents);
|
||||
}
|
||||
|
||||
onForwardingHistoryFetchCL() {
|
||||
if (undefined === this.endDate || this.endDate == null) {
|
||||
this.endDate = new Date();
|
||||
}
|
||||
if (undefined === this.startDate || this.startDate == null) {
|
||||
this.startDate = new Date(this.endDate.getFullYear(), this.endDate.getMonth(), this.endDate.getDate() - 1);
|
||||
}
|
||||
this.store.dispatch(new RTLActions.GetForwardingHistoryCL(
|
||||
// { end_time: Math.round(this.endDate.getTime() / 1000).toString(), start_time: Math.round(this.startDate.getTime() / 1000).toString() }
|
||||
));
|
||||
}
|
||||
|
||||
resetData() {
|
||||
// this.endDate = new Date();
|
||||
// this.startDate = new Date(this.endDate.getFullYear(), this.endDate.getMonth(), this.endDate.getDate() - 1);
|
||||
if (undefined !== this.forwardingHistoryEvents) {
|
||||
this.forwardingHistoryEvents.data = [];
|
||||
}
|
||||
}
|
||||
|
||||
applyFilter(selFilter: string) {
|
||||
this.forwardingHistoryEvents.filter = selFilter;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.resetData();
|
||||
this.unsub.forEach(completeSub => {
|
||||
completeSub.next();
|
||||
completeSub.complete();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch">
|
||||
<div>
|
||||
<h4 fxLayoutAlign="start" class="dashboard-info-title">Lightning</h4>
|
||||
<div class="overflow-wrap dashboard-info-value">{{balances.lightning | number}} Sats</div>
|
||||
<mat-progress-bar class="dashboard-progress-bar" mode="determinate" value="{{balances.lightning/balances.total*100}}"></mat-progress-bar>
|
||||
</div>
|
||||
<div>
|
||||
<h4 fxLayoutAlign="start" class="dashboard-info-title">On-chain</h4>
|
||||
<div class="overflow-wrap dashboard-info-value">{{balances.onchain | number}} Sats</div>
|
||||
<mat-progress-bar class="dashboard-progress-bar" mode="determinate" value="{{balances.onchain/balances.total*100}}"></mat-progress-bar>
|
||||
</div>
|
||||
<div>
|
||||
<h4 fxLayoutAlign="start" class="dashboard-info-title">Total</h4>
|
||||
<div class="overflow-wrap dashboard-info-value">{{balances.total | number}} Sats</div>
|
||||
</div>
|
||||
</div>
|
@ -1,20 +1,20 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ChannelClosedComponent } from './channel-closed.component';
|
||||
import { CLBalancesInfoComponent } from './balances-info.component';
|
||||
|
||||
describe('ChannelClosedComponent', () => {
|
||||
let component: ChannelClosedComponent;
|
||||
let fixture: ComponentFixture<ChannelClosedComponent>;
|
||||
describe('CLBalancesInfoComponent', () => {
|
||||
let component: CLBalancesInfoComponent;
|
||||
let fixture: ComponentFixture<CLBalancesInfoComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ChannelClosedComponent ]
|
||||
declarations: [ CLBalancesInfoComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ChannelClosedComponent);
|
||||
fixture = TestBed.createComponent(CLBalancesInfoComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
@ -0,0 +1,13 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-cl-balances-info',
|
||||
templateUrl: './balances-info.component.html',
|
||||
styleUrls: ['./balances-info.component.scss']
|
||||
})
|
||||
export class CLBalancesInfoComponent {
|
||||
@Input() balances = { onchain: 0, lightning: 0, total: 0 };
|
||||
|
||||
constructor() {}
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
<div fxLayout="column" fxLayoutAlign="space-between stretch" fxFlex="100">
|
||||
<div fxLayout="column" fxFlex="9" fxLayoutAlign="end start">
|
||||
<span class="dashboard-capacity-header this-channel-capacity">Total Capacity</span>
|
||||
<div fxLayout="row" fxLayoutAlign="space-between start" class="w-100">
|
||||
<mat-hint fxFlex="40" fxLayoutAlign="start center" class="font-size-90"><strong class="font-weight-900 mr-5px">Local:</strong>{{channelBalances.localBalance || 0 | number:'1.0-0'}} Sats</mat-hint>
|
||||
<mat-hint fxFlex="20" fxLayoutAlign="center center" class="font-size-90">
|
||||
<fa-icon [icon]="faBalanceScale" class="mr-3px" matTooltip="Balance Score"></fa-icon>
|
||||
({{channelBalances.balancedness || 0 | number}})
|
||||
</mat-hint>
|
||||
<mat-hint fxFlex="40" fxLayoutAlign="end center" class="font-size-90"><strong class="font-weight-900 mr-5px">Remote:</strong>{{channelBalances.remoteBalance || 0 | number:'1.0-0'}} Sats</mat-hint>
|
||||
</div>
|
||||
<mat-progress-bar class="dashboard-progress-bar this-channel-bar" mode="determinate" color="accent" value="{{channelBalances.localBalance && channelBalances.localBalance > 0 ? ((+channelBalances.localBalance/((+channelBalances.localBalance)+(+channelBalances.remoteBalance)))*100) : 0}}"></mat-progress-bar>
|
||||
</div>
|
||||
<div fxLayout="column" fxFlex="3" fxLayoutAlign="end stretch"><mat-divider class="dashboard-divider"></mat-divider></div>
|
||||
<div fxLayout="column" fxFlex.gt-sm="88" fxFlex="84" fxLayoutAlign="start start" class="channels-capacity-scroll" perfectScrollbar>
|
||||
<div fxLayout="column" fxFlex="100" class="w-100" *ngIf="allChannels && allChannels.length > 0; else noChannelBlock">
|
||||
<div *ngFor="let channel of allChannels" class="mt-2">
|
||||
<span class="dashboard-capacity-header" matTooltip="{{channel.alias || channel.id}}" matTooltipDisabled="{{(channel.alias || channel.id).length < 26}}">{{(channel.alias || channel.id) | slice:0:24}}{{(channel.alias || channel.id).length > 25 ? '...' : ''}}</span>
|
||||
<div fxLayout="row" fxLayoutAlign="space-between start" class="w-100">
|
||||
<mat-hint fxFlex="40" fxLayoutAlign="start center" class="font-size-90 color-primary"><strong class="font-weight-900 mr-5px">Local:</strong>{{channel.msatoshi_to_us/1000 || 0 | number:'1.0-0'}} Sats</mat-hint>
|
||||
<mat-hint fxFlex="20" fxLayoutAlign="center center" class="font-size-90 color-primary">
|
||||
<fa-icon [icon]="faBalanceScale" class="color-primary mr-3px" matTooltip="Balance Score"></fa-icon>
|
||||
({{channel.balancedness || 0 | number}})
|
||||
</mat-hint>
|
||||
<mat-hint fxFlex="40" fxLayoutAlign="end center" class="font-size-90 color-primary"><strong class="font-weight-900 mr-5px">Remote:</strong>{{channel.msatoshi_to_them/1000 || 0 | number:'1.0-0'}} Sats</mat-hint>
|
||||
</div>
|
||||
<mat-progress-bar class="dashboard-progress-bar" mode="determinate" value="{{channel.msatoshi_to_us && channel.msatoshi_to_us > 0 ? ((+channel.msatoshi_to_us/((+channel.msatoshi_to_us)+(+channel.msatoshi_to_them)))*100) : 0}}"></mat-progress-bar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ng-template #noChannelBlock>
|
||||
<div fxLayout="row" fxFlex="10" fxLayoutAlign="space-between center" class="w-100 mt-1">
|
||||
No channels available.
|
||||
<button fxFlex="33" fxLayoutAlign="center center" mat-stroked-button color="primary" (click)="goToChannels()" tabindex="1">Open Channel</button>
|
||||
</div>
|
||||
</ng-template>
|
@ -0,0 +1,5 @@
|
||||
.channels-capacity-scroll {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: hidden;
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CLChannelCapacityInfoComponent } from './channel-capacity-info.component';
|
||||
|
||||
describe('CLChannelCapacityInfoComponent', () => {
|
||||
let component: CLChannelCapacityInfoComponent;
|
||||
let fixture: ComponentFixture<CLChannelCapacityInfoComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CLChannelCapacityInfoComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CLChannelCapacityInfoComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,25 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { faBalanceScale, faDumbbell } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
import { ChannelCL } from '../../../shared/models/clModels';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-cl-channel-capacity-info',
|
||||
templateUrl: './channel-capacity-info.component.html',
|
||||
styleUrls: ['./channel-capacity-info.component.scss']
|
||||
})
|
||||
export class CLChannelCapacityInfoComponent {
|
||||
public faBalanceScale = faBalanceScale;
|
||||
public faDumbbell = faDumbbell;
|
||||
@Input() channelBalances: {localBalance: number, remoteBalance: number, balancedness: string};
|
||||
@Input() allChannels: ChannelCL[];
|
||||
@Input() sortBy: string = 'Balance Score';
|
||||
|
||||
constructor(private router: Router) {}
|
||||
|
||||
goToChannels() {
|
||||
this.router.navigateByUrl('/lnd/peerschannels');
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
<div fxLayout="column" fxLayoutAlign="space-between stretch" fxFlex="100">
|
||||
<div fxLayout="column" fxFlex="9" fxLayoutAlign="end start">
|
||||
<span class="dashboard-capacity-header this-channel-capacity">Total Capacity</span>
|
||||
<mat-hint class="font-size-90">{{totalLiquidity | number:'1.0-0'}} Sats</mat-hint>
|
||||
<mat-progress-bar class="dashboard-progress-bar this-channel-bar" mode="determinate" color="accent" value="100"></mat-progress-bar>
|
||||
</div>
|
||||
<div fxLayout="column" fxFlex="3" fxLayoutAlign="end stretch"><mat-divider class="dashboard-divider"></mat-divider></div>
|
||||
<div fxLayout="column" fxFlex.gt-sm="88" fxFlex="84" fxLayoutAlign="start start" class="channels-capacity-scroll" perfectScrollbar>
|
||||
<div fxLayout="column" fxFlex="100" class="w-100" *ngIf="allChannels && allChannels.length > 0; else noChannelBlock">
|
||||
<div *ngFor="let channel of allChannels" class="mt-2">
|
||||
<span class="dashboard-capacity-header" matTooltip="{{channel.alias || channel.id}}" matTooltipDisabled="{{(channel.alias || channel.id).length < 26}}">{{(channel.alias || channel.id) | slice:0:24}}{{(channel.alias || channel.id).length > 25 ? '...' : ''}}</span>
|
||||
<div fxLayout="row" fxLayoutAlign="space-between start" class="w-100">
|
||||
<mat-hint *ngIf="direction === 'In'" fxFlex="100" fxLayoutAlign="start center" class="font-size-90 color-primary"><strong class="font-weight-900 mr-5px">Capacity: </strong>{{channel.msatoshi_to_them/1000 || 0 | number:'1.0-0'}} Sats</mat-hint>
|
||||
<mat-hint *ngIf="direction === 'Out'" fxFlex="100" fxLayoutAlign="start center" class="font-size-90 color-primary"><strong class="font-weight-900 mr-5px">Capacity: </strong>{{channel.msatoshi_to_us/1000 || 0 | number:'1.0-0'}} Sats</mat-hint>
|
||||
</div>
|
||||
<mat-progress-bar *ngIf="direction === 'In'" class="dashboard-progress-bar" mode="determinate" value="{{(totalLiquidity > 0) ? ((+channel.msatoshi_to_them/1000 || 0)/(totalLiquidity) * 100) : 0}}"></mat-progress-bar>
|
||||
<mat-progress-bar *ngIf="direction === 'Out'" class="dashboard-progress-bar" mode="determinate" value="{{(totalLiquidity > 0) ? ((+channel.msatoshi_to_us/1000 || 0)/(totalLiquidity) * 100) : 0}}"></mat-progress-bar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ng-template #noChannelBlock>
|
||||
<div fxLayout="row" fxFlex="10" fxLayoutAlign="space-between center" class="w-100 mt-1">
|
||||
No channels available.
|
||||
<button fxFlex="33" fxLayoutAlign="center center" mat-stroked-button color="primary" (click)="goToChannels()" tabindex="1">Open Channel</button>
|
||||
</div>
|
||||
</ng-template>
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CLChannelLiquidityInfoComponent } from './channel-liquidity-info.component';
|
||||
|
||||
describe('CLChannelLiquidityInfoComponent', () => {
|
||||
let component: CLChannelLiquidityInfoComponent;
|
||||
let fixture: ComponentFixture<CLChannelLiquidityInfoComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CLChannelLiquidityInfoComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CLChannelLiquidityInfoComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,22 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { ChannelCL } from '../../../shared/models/clModels';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-cl-channel-liquidity-info',
|
||||
templateUrl: './channel-liquidity-info.component.html',
|
||||
styleUrls: ['./channel-liquidity-info.component.scss']
|
||||
})
|
||||
export class CLChannelLiquidityInfoComponent {
|
||||
@Input() direction: string;
|
||||
@Input() totalLiquidity: number;
|
||||
@Input() allChannels: ChannelCL[];
|
||||
|
||||
constructor(private router: Router) {}
|
||||
|
||||
goToChannels() {
|
||||
this.router.navigateByUrl('/lnd/peerschannels');
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
<div fxLayout="column" fxFlex="50" fxLayoutAlign="space-between stretch">
|
||||
<div>
|
||||
<h4 fxLayoutAlign="start" class="dashboard-info-title">Active</h4>
|
||||
<div class="overflow-wrap dashboard-info-value"><span class="dot tiny-dot green"></span>{{(channelsStatus.active.channels || 0) | number}}</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 fxLayoutAlign="start" class="dashboard-info-title">Pending</h4>
|
||||
<div class="overflow-wrap dashboard-info-value"><span class="dot tiny-dot yellow"></span>{{(channelsStatus.pending.channels || 0) | number}}</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 fxLayoutAlign="start" class="dashboard-info-title">Inactive</h4>
|
||||
<div class="overflow-wrap dashboard-info-value"><span class="dot tiny-dot grey"></span>{{(channelsStatus.inactive.channels || 0) | number}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div fxLayout="column" fxFlex="50" fxLayoutAlign="space-between stretch">
|
||||
<div>
|
||||
<h4 fxLayoutAlign="start" class="dashboard-info-title">Capacity</h4>
|
||||
<div class="overflow-wrap dashboard-info-value">{{(channelsStatus.active.capacity || 0) | number}} Sats</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 fxLayoutAlign="start" class="dashboard-info-title">Capacity</h4>
|
||||
<div class="overflow-wrap dashboard-info-value">{{(channelsStatus.pending.capacity || 0) | number}} Sats</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 fxLayoutAlign="start" class="dashboard-info-title">Capacity</h4>
|
||||
<div class="overflow-wrap dashboard-info-value">{{(channelsStatus.inactive.capacity || 0) | number}} Sats</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CLChannelStatusInfoComponent } from './channel-status-info.component';
|
||||
|
||||
describe('CLChannelStatusInfoComponent', () => {
|
||||
let component: CLChannelStatusInfoComponent;
|
||||
let fixture: ComponentFixture<CLChannelStatusInfoComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CLChannelStatusInfoComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CLChannelStatusInfoComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,16 @@
|
||||
import { Component, OnChanges, Input } from '@angular/core';
|
||||
import { ChannelsStatusCL } from '../../../shared/models/clModels';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-cl-channel-status-info',
|
||||
templateUrl: './channel-status-info.component.html',
|
||||
styleUrls: ['./channel-status-info.component.scss']
|
||||
})
|
||||
export class CLChannelStatusInfoComponent implements OnChanges {
|
||||
@Input() channelsStatus: ChannelsStatusCL = {};
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnChanges() {}
|
||||
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
<div fxLayout="column" fxFlex="50" fxLayoutAlign="space-between stretch">
|
||||
<div>
|
||||
<h4 fxLayoutAlign="start" class="dashboard-info-title">Total</h4>
|
||||
<div class="overflow-wrap dashboard-info-value">{{fees?.feeCollected/1000 | number}} Sats</div>
|
||||
</div>
|
||||
</div>
|
||||
<div fxLayout="column" fxFlex="50" fxLayoutAlign="space-between stretch">
|
||||
<div>
|
||||
<h4 fxLayoutAlign="start" class="dashboard-info-title">Transactions</h4>
|
||||
<div class="overflow-wrap dashboard-info-value">{{fees?.totalTxCount | number}}</div>
|
||||
</div>
|
||||
</div>
|
@ -1,20 +1,20 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CLInvoicesComponent } from './invoices.component';
|
||||
import { CLFeeInfoComponent } from './fee-info.component';
|
||||
|
||||
describe('CLInvoicesComponent', () => {
|
||||
let component: CLInvoicesComponent;
|
||||
let fixture: ComponentFixture<CLInvoicesComponent>;
|
||||
describe('CLFeeInfoComponent', () => {
|
||||
let component: CLFeeInfoComponent;
|
||||
let fixture: ComponentFixture<CLFeeInfoComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CLInvoicesComponent ]
|
||||
declarations: [ CLFeeInfoComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CLInvoicesComponent);
|
||||
fixture = TestBed.createComponent(CLFeeInfoComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
@ -0,0 +1,13 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { FeesCL } from '../../../shared/models/clModels';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-cl-fee-info',
|
||||
templateUrl: './fee-info.component.html',
|
||||
styleUrls: ['./fee-info.component.scss']
|
||||
})
|
||||
export class CLFeeInfoComponent {
|
||||
@Input() fees: FeesCL;
|
||||
totalFees = [{'name': 'Total', 'value': 0}];
|
||||
|
||||
}
|