diff options
Diffstat (limited to 'applications/luci-app-dockerman')
29 files changed, 4034 insertions, 3438 deletions
diff --git a/applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/containers.svg b/applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/containers.svg new file mode 100644 index 0000000000..4165f90bdc --- /dev/null +++ b/applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/containers.svg @@ -0,0 +1,7 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> + +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <title>Docker icon</title> + <path d="M4.82 17.275c-.684 0-1.304-.56-1.304-1.24s.56-1.243 1.305-1.243c.748 0 1.31.56 1.31 1.242s-.622 1.24-1.305 1.24zm16.012-6.763c-.135-.992-.75-1.8-1.56-2.42l-.315-.25-.254.31c-.494.56-.69 1.553-.63 2.295.06.562.24 1.12.554 1.554-.254.13-.568.25-.81.377-.57.187-1.124.25-1.68.25H.097l-.06.37c-.12 1.182.06 2.42.562 3.54l.244.435v.06c1.5 2.483 4.17 3.6 7.078 3.6 5.594 0 10.182-2.42 12.357-7.633 1.425.062 2.864-.31 3.54-1.676l.18-.31-.3-.187c-.81-.494-1.92-.56-2.85-.31l-.018.002zm-8.008-.992h-2.428v2.42h2.43V9.518l-.002.003zm0-3.043h-2.428v2.42h2.43V6.48l-.002-.003zm0-3.104h-2.428v2.42h2.43v-2.42h-.002zm2.97 6.147H13.38v2.42h2.42V9.518l-.007.003zm-8.998 0H4.383v2.42h2.422V9.518l-.01.003zm3.03 0h-2.4v2.42H9.84V9.518l-.015.003zm-6.03 0H1.4v2.42h2.428V9.518l-.03.003zm6.03-3.043h-2.4v2.42H9.84V6.48l-.015-.003zm-3.045 0H4.387v2.42H6.8V6.48l-.016-.003z" /> +</svg> diff --git a/applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/images.svg b/applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/images.svg new file mode 100644 index 0000000000..90ca5a1c7d --- /dev/null +++ b/applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/images.svg @@ -0,0 +1,9 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> + +<svg xmlns="http://www.w3.org/2000/svg" id="icon-hub" viewBox="0 -4 42 50" stroke-width="2" fill-rule="nonzero" width="100%" height="100%"> + <path d="M37.176371,36.2324812 C37.1920117,36.8041095 36.7372743,37.270685 36.1684891,37.270685 L3.74335204,37.2703476 C3.17827583,37.2703476 2.72400056,36.8091818 2.72400056,36.2397767 L2.72400056,19.6131383 C1.4312007,18.4881431 0.662551336,16.8884326 0.662551336,15.1618249 L0.664207893,14.69503 C0.63774183,14.4532127 0.650524255,14.2942438 0.711604827,14.1238231 L5.10793246,1.20935468 C5.24853286,0.797020623 5.63848594,0.511627907 6.06681069,0.511627907 L34.0728364,0.511627907 C34.5091607,0.511627907 34.889927,0.793578201 35.0316653,1.20921034 L39.4428567,14.1234095 C39.4871296,14.273204 39.5020782,14.4249444 39.4884726,14.5493649 L39.4884726,15.1505835 C39.4884726,16.9959517 38.6190601,18.6883031 37.1764746,19.7563084 L37.176371,36.2324812 Z M35.1376208,35.209311 L35.1376208,20.7057152 C34.7023924,20.8097593 34.271333,20.8633641 33.8336069,20.8633641 C32.0046019,20.8633641 30.3013756,19.9547008 29.2437221,18.4771538 C28.1860473,19.954695 26.4828515,20.8633641 24.6538444,20.8633641 C22.824803,20.8633641 21.1216155,19.9547157 20.0639591,18.4771544 C19.0062842,19.9546953 17.3030887,20.8633641 15.4740818,20.8633641 C13.6450404,20.8633641 11.9418529,19.9547157 10.8841965,18.4771544 C9.82652161,19.9546953 8.12332608,20.8633641 6.29431919,20.8633641 C5.76735555,20.8633641 5.24095778,20.7883418 4.73973398,20.644674 L4.73973398,35.209311 L35.1376208,35.209311 Z M30.2720226,15.6557626 C30.5154632,17.4501192 32.0503909,18.8018554 33.845083,18.8018554 C35.7286794,18.8018554 37.285413,17.3395134 37.4474599,15.4751932 L30.2280765,15.4751932 C30.2470638,15.532987 30.2617919,15.5932958 30.2720226,15.6557626 Z M21.0484306,15.4751932 C21.0674179,15.532987 21.0821459,15.5932958 21.0923767,15.6557626 C21.3358173,17.4501192 22.8707449,18.8018554 24.665437,18.8018554 C26.4601001,18.8018554 27.9950169,17.4501481 28.2378191,15.6611556 C28.2451225,15.5981318 28.2590045,15.5358056 28.2787375,15.4751932 L21.0484306,15.4751932 Z M11.9238102,15.6557626 C12.1672508,17.4501192 13.7021785,18.8018554 15.4968705,18.8018554 C17.2915336,18.8018554 18.8264505,17.4501481 19.0692526,15.6611556 C19.0765561,15.5981318 19.0904381,15.5358056 19.110171,15.4751932 L11.8798641,15.4751932 C11.8988514,15.532987 11.9135795,15.5932958 11.9238102,15.6557626 Z M6.31682805,18.8018317 C8.11149114,18.8018317 9.64640798,17.4501244 9.88921012,15.6611319 C9.89651357,15.5981081 9.91039559,15.5357819 9.93012856,15.4751696 L2.70318796,15.4751696 C2.86612006,17.3346852 4.42809696,18.8018317 6.31682805,18.8018317 Z M3.09670082,13.4139924 L37.04257,13.4139924 L33.3489482,2.57204736 L6.80119239,2.57204736 L3.09670082,13.4139924 Z" + id="Fill-1"></path> + <rect id="Rectangle-3" x="14" y="26" width="6" height="10"></rect> + <path d="M20,26 L20,36 L26,36 L26,26 L20,26 Z" id="Rectangle-3"></path> +</svg> diff --git a/applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/networks.svg b/applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/networks.svg new file mode 100644 index 0000000000..3eb12a3936 --- /dev/null +++ b/applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/networks.svg @@ -0,0 +1,12 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> + +<svg xmlns="http://www.w3.org/2000/svg" version="1.1" x="0px" y="0px" width="100%" height="100%" viewBox="0 0 48.723 48.723" xml:space="preserve"> + <path d="M7.452,24.152h3.435v5.701h0.633c0.001,0,0.001,0,0.002,0h0.636v-5.701h3.51v-1.059h17.124v1.104h3.178v5.656h0.619 c0,0,0,0,0.002,0h0.619v-5.656h3.736v-0.856c0-0.012,0.006-0.021,0.006-0.032c0-0.072,0-0.143,0-0.215h5.721v-1.316h-5.721 c0-0.054,0-0.108,0-0.164c0-0.011-0.006-0.021-0.006-0.032v-0.832h-8.154v1.028h-7.911v-2.652h-0.689c-0.001,0-0.001,0-0.002,0 h-0.678v2.652h-7.846v-1.104H7.452v1.104H1.114v1.316h6.338V24.152z" /> + <path d="M21.484,16.849h5.204v-2.611h7.133V1.555H14.588v12.683h6.896V16.849z M16.537,12.288V3.505h15.335v8.783H16.537z" /> + <rect x="18.682" y="16.898" width="10.809" height="0.537" /> + <path d="M0,43.971h6.896v2.611H12.1v-2.611h7.134V31.287H0V43.971z M1.95,33.236h15.334v8.785H1.95V33.236z" /> + <rect x="4.095" y="46.631" width="10.808" height="0.537" /> + <path d="M29.491,30.994v12.684h6.895v2.611h5.205v-2.611h7.133V30.994H29.491z M46.774,41.729H31.44v-8.783h15.334V41.729z" /> + <rect x="33.584" y="46.338" width="10.809" height="0.537" /> +</svg> diff --git a/applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/volumes.svg b/applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/volumes.svg new file mode 100644 index 0000000000..50292a2025 --- /dev/null +++ b/applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/volumes.svg @@ -0,0 +1,6 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> + +<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 55 55" style="enable-background:new 0 0 55 55;" xml:space="preserve"> + <path d="M52.354,8.51C51.196,4.22,42.577,0,27.5,0C12.423,0,3.803,4.22,2.646,8.51C2.562,8.657,2.5,8.818,2.5,9v0.5V21v0.5V22v11 v0.5V34v12c0,0.162,0.043,0.315,0.117,0.451C3.798,51.346,14.364,55,27.5,55c13.106,0,23.655-3.639,24.875-8.516 C52.455,46.341,52.5,46.176,52.5,46V34v-0.5V33V22v-0.5V21V9.5V9C52.5,8.818,52.438,8.657,52.354,8.51z M50.421,33.985 c-0.028,0.121-0.067,0.241-0.116,0.363c-0.04,0.099-0.089,0.198-0.143,0.297c-0.067,0.123-0.142,0.246-0.231,0.369 c-0.066,0.093-0.141,0.185-0.219,0.277c-0.111,0.131-0.229,0.262-0.363,0.392c-0.081,0.079-0.17,0.157-0.26,0.236 c-0.164,0.143-0.335,0.285-0.526,0.426c-0.082,0.061-0.17,0.12-0.257,0.18c-0.226,0.156-0.462,0.311-0.721,0.463 c-0.068,0.041-0.141,0.08-0.212,0.12c-0.298,0.168-0.609,0.335-0.945,0.497c-0.043,0.021-0.088,0.041-0.132,0.061 c-0.375,0.177-0.767,0.351-1.186,0.519c-0.012,0.005-0.024,0.009-0.036,0.014c-2.271,0.907-5.176,1.67-8.561,2.17 c-0.017,0.002-0.034,0.004-0.051,0.007c-0.658,0.097-1.333,0.183-2.026,0.259c-0.113,0.012-0.232,0.02-0.346,0.032 c-0.605,0.063-1.217,0.121-1.847,0.167c-0.288,0.021-0.59,0.031-0.883,0.049c-0.474,0.028-0.943,0.059-1.429,0.076 C29.137,40.984,28.327,41,27.5,41s-1.637-0.016-2.432-0.044c-0.486-0.017-0.955-0.049-1.429-0.076 c-0.293-0.017-0.595-0.028-0.883-0.049c-0.63-0.046-1.242-0.104-1.847-0.167c-0.114-0.012-0.233-0.02-0.346-0.032 c-0.693-0.076-1.368-0.163-2.026-0.259c-0.017-0.002-0.034-0.004-0.051-0.007c-3.385-0.5-6.29-1.263-8.561-2.17 c-0.012-0.004-0.024-0.009-0.036-0.014c-0.419-0.168-0.812-0.342-1.186-0.519c-0.043-0.021-0.089-0.041-0.132-0.061 c-0.336-0.162-0.647-0.328-0.945-0.497c-0.07-0.04-0.144-0.079-0.212-0.12c-0.259-0.152-0.495-0.307-0.721-0.463 c-0.086-0.06-0.175-0.119-0.257-0.18c-0.191-0.141-0.362-0.283-0.526-0.426c-0.089-0.078-0.179-0.156-0.26-0.236 c-0.134-0.13-0.252-0.26-0.363-0.392c-0.078-0.092-0.153-0.184-0.219-0.277c-0.088-0.123-0.163-0.246-0.231-0.369 c-0.054-0.099-0.102-0.198-0.143-0.297c-0.049-0.121-0.088-0.242-0.116-0.363C4.541,33.823,4.5,33.661,4.5,33.5 c0-0.113,0.013-0.226,0.031-0.338c0.025-0.151,0.011-0.302-0.031-0.445v-7.424c0.028,0.026,0.063,0.051,0.092,0.077 c0.218,0.192,0.44,0.383,0.69,0.567C9.049,28.786,16.582,31,27.5,31c10.872,0,18.386-2.196,22.169-5.028 c0.302-0.22,0.574-0.447,0.83-0.678l0.001-0.001v7.424c-0.042,0.143-0.056,0.294-0.031,0.445c0.019,0.112,0.031,0.225,0.031,0.338 C50.5,33.661,50.459,33.823,50.421,33.985z M50.5,13.293v7.424c-0.042,0.143-0.056,0.294-0.031,0.445 c0.019,0.112,0.031,0.225,0.031,0.338c0,0.161-0.041,0.323-0.079,0.485c-0.028,0.121-0.067,0.241-0.116,0.363 c-0.04,0.099-0.089,0.198-0.143,0.297c-0.067,0.123-0.142,0.246-0.231,0.369c-0.066,0.093-0.141,0.185-0.219,0.277 c-0.111,0.131-0.229,0.262-0.363,0.392c-0.081,0.079-0.17,0.157-0.26,0.236c-0.164,0.143-0.335,0.285-0.526,0.426 c-0.082,0.061-0.17,0.12-0.257,0.18c-0.226,0.156-0.462,0.311-0.721,0.463c-0.068,0.041-0.141,0.08-0.212,0.12 c-0.298,0.168-0.609,0.335-0.945,0.497c-0.043,0.021-0.088,0.041-0.132,0.061c-0.375,0.177-0.767,0.351-1.186,0.519 c-0.012,0.005-0.024,0.009-0.036,0.014c-2.271,0.907-5.176,1.67-8.561,2.17c-0.017,0.002-0.034,0.004-0.051,0.007 c-0.658,0.097-1.333,0.183-2.026,0.259c-0.113,0.012-0.232,0.02-0.346,0.032c-0.605,0.063-1.217,0.121-1.847,0.167 c-0.288,0.021-0.59,0.031-0.883,0.049c-0.474,0.028-0.943,0.059-1.429,0.076C29.137,28.984,28.327,29,27.5,29 s-1.637-0.016-2.432-0.044c-0.486-0.017-0.955-0.049-1.429-0.076c-0.293-0.017-0.595-0.028-0.883-0.049 c-0.63-0.046-1.242-0.104-1.847-0.167c-0.114-0.012-0.233-0.02-0.346-0.032c-0.693-0.076-1.368-0.163-2.026-0.259 c-0.017-0.002-0.034-0.004-0.051-0.007c-3.385-0.5-6.29-1.263-8.561-2.17c-0.012-0.004-0.024-0.009-0.036-0.014 c-0.419-0.168-0.812-0.342-1.186-0.519c-0.043-0.021-0.089-0.041-0.132-0.061c-0.336-0.162-0.647-0.328-0.945-0.497 c-0.07-0.04-0.144-0.079-0.212-0.12c-0.259-0.152-0.495-0.307-0.721-0.463c-0.086-0.06-0.175-0.119-0.257-0.18 c-0.191-0.141-0.362-0.283-0.526-0.426c-0.089-0.078-0.179-0.156-0.26-0.236c-0.134-0.13-0.252-0.26-0.363-0.392 c-0.078-0.092-0.153-0.184-0.219-0.277c-0.088-0.123-0.163-0.246-0.231-0.369c-0.054-0.099-0.102-0.198-0.143-0.297 c-0.049-0.121-0.088-0.242-0.116-0.363C4.541,21.823,4.5,21.661,4.5,21.5c0-0.113,0.013-0.226,0.031-0.338 c0.025-0.151,0.011-0.302-0.031-0.445v-7.424c0.12,0.109,0.257,0.216,0.387,0.324c0.072,0.06,0.139,0.12,0.215,0.18 c0.3,0.236,0.624,0.469,0.975,0.696c0.073,0.047,0.155,0.093,0.231,0.14c0.294,0.183,0.605,0.362,0.932,0.538 c0.121,0.065,0.242,0.129,0.367,0.193c0.365,0.186,0.748,0.367,1.151,0.542c0.066,0.029,0.126,0.059,0.193,0.087 c0.469,0.199,0.967,0.389,1.485,0.573c0.143,0.051,0.293,0.099,0.44,0.149c0.412,0.139,0.838,0.272,1.279,0.401 c0.159,0.046,0.315,0.094,0.478,0.138c0.585,0.162,1.189,0.316,1.823,0.458c0.087,0.02,0.181,0.036,0.269,0.055 c0.559,0.122,1.139,0.235,1.735,0.341c0.202,0.036,0.407,0.07,0.613,0.104c0.567,0.093,1.151,0.178,1.75,0.256 c0.154,0.02,0.301,0.043,0.457,0.062c0.744,0.09,1.514,0.167,2.305,0.233c0.195,0.016,0.398,0.028,0.596,0.042 c0.633,0.046,1.28,0.084,1.942,0.114c0.241,0.011,0.481,0.022,0.727,0.031C25.712,18.979,26.59,19,27.5,19s1.788-0.021,2.65-0.05 c0.245-0.009,0.485-0.02,0.727-0.031c0.662-0.03,1.309-0.068,1.942-0.114c0.198-0.015,0.4-0.026,0.596-0.042 c0.791-0.065,1.561-0.143,2.305-0.233c0.156-0.019,0.303-0.042,0.457-0.062c0.599-0.078,1.182-0.163,1.75-0.256 c0.206-0.034,0.411-0.068,0.613-0.104c0.596-0.106,1.176-0.219,1.735-0.341c0.088-0.019,0.182-0.036,0.269-0.055 c0.634-0.142,1.238-0.297,1.823-0.458c0.163-0.045,0.319-0.092,0.478-0.138c0.441-0.129,0.867-0.262,1.279-0.401 c0.147-0.05,0.297-0.098,0.44-0.149c0.518-0.184,1.017-0.374,1.485-0.573c0.067-0.028,0.127-0.058,0.193-0.087 c0.403-0.176,0.786-0.356,1.151-0.542c0.125-0.064,0.247-0.128,0.367-0.193c0.327-0.175,0.638-0.354,0.932-0.538 c0.076-0.047,0.158-0.093,0.231-0.14c0.351-0.227,0.675-0.459,0.975-0.696c0.075-0.06,0.142-0.12,0.215-0.18 C50.243,13.509,50.38,13.402,50.5,13.293z M27.5,2c13.555,0,23,3.952,23,7.5s-9.445,7.5-23,7.5s-23-3.952-23-7.5S13.945,2,27.5,2z M50.5,45.703c-0.014,0.044-0.024,0.089-0.032,0.135C49.901,49.297,40.536,53,27.5,53S5.099,49.297,4.532,45.838 c-0.008-0.045-0.019-0.089-0.032-0.131v-8.414c0.028,0.026,0.063,0.051,0.092,0.077c0.218,0.192,0.44,0.383,0.69,0.567 C9.049,40.786,16.582,43,27.5,43c10.872,0,18.386-2.196,22.169-5.028c0.302-0.22,0.574-0.447,0.83-0.678l0.001-0.001V45.703z" /> +</svg> diff --git a/applications/luci-app-dockerman/luasrc/controller/dockerman.lua b/applications/luci-app-dockerman/luasrc/controller/dockerman.lua index aabad3360d..608fbf4b01 100644 --- a/applications/luci-app-dockerman/luasrc/controller/dockerman.lua +++ b/applications/luci-app-dockerman/luasrc/controller/dockerman.lua @@ -2,386 +2,444 @@ LuCI - Lua Configuration Interface Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman> ]]-- -require "luci.util" + local docker = require "luci.model.docker" --- local uci = require "luci.model.uci" module("luci.controller.dockerman",package.seeall) function index() + local e = entry({"admin", "docker"}, firstchild(), "Docker", 40) + e.dependent = false + e.acl_depends = { "luci-app-dockerman" } + + entry({"admin", "docker", "overview"},cbi("dockerman/overview"),_("Overview"),0).leaf=true + + local remote = luci.model.uci.cursor():get_bool("dockerd", "globals", "remote_endpoint") + if remote then + local host = luci.model.uci.cursor():get("dockerd", "globals", "remote_host") + local port = luci.model.uci.cursor():get("dockerd", "globals", "remote_port") + if not host or not port then + return + end + else + local socket = luci.model.uci.cursor():get("dockerd", "globals", "socket_path") + if socket and not nixio.fs.access(socket) then + return + end + end + + if (require "luci.model.docker").new():_ping().code ~= 200 then + return + end + + entry({"admin", "docker", "containers"}, form("dockerman/containers"), _("Containers"),1).leaf=true + entry({"admin", "docker", "images"}, form("dockerman/images"), _("Images"),2).leaf=true + entry({"admin", "docker", "networks"}, form("dockerman/networks"), _("Networks"),3).leaf=true + entry({"admin", "docker", "volumes"}, form("dockerman/volumes"), _("Volumes"),4).leaf=true + entry({"admin", "docker", "events"}, call("action_events"), _("Events"),5) - local e = entry({"admin", "docker"}, firstchild(), "Docker", 40) - e.dependent = false - e.acl_depends = { "luci-app-dockerman" } - - entry({"admin","docker","overview"},cbi("dockerman/overview"),_("Overview"),0).leaf=true - - local remote = luci.model.uci.cursor():get("dockerman", "local", "remote_endpoint") - if remote == nil then - local socket = luci.model.uci.cursor():get("dockerman", "local", "socket_path") - if socket and not nixio.fs.access(socket) then return end - elseif remote == "true" then - local host = luci.model.uci.cursor():get("dockerman", "local", "remote_host") - local port = luci.model.uci.cursor():get("dockerman", "local", "remote_port") - if not host or not port then return end - end - - if (require "luci.model.docker").new():_ping().code ~= 200 then return end - entry({"admin","docker","containers"},form("dockerman/containers"),_("Containers"),1).leaf=true - entry({"admin","docker","images"},form("dockerman/images"),_("Images"),2).leaf=true - entry({"admin","docker","networks"},form("dockerman/networks"),_("Networks"),3).leaf=true - entry({"admin","docker","volumes"},form("dockerman/volumes"),_("Volumes"),4).leaf=true - entry({"admin","docker","events"},call("action_events"),_("Events"),5) - entry({"admin","docker","newcontainer"},form("dockerman/newcontainer")).leaf=true - entry({"admin","docker","newnetwork"},form("dockerman/newnetwork")).leaf=true - entry({"admin","docker","container"},form("dockerman/container")).leaf=true - entry({"admin","docker","container_stats"},call("action_get_container_stats")).leaf=true - entry({"admin","docker","container_get_archive"},call("download_archive")).leaf=true - entry({"admin","docker","container_put_archive"},call("upload_archive")).leaf=true - entry({"admin","docker","images_save"},call("save_images")).leaf=true - entry({"admin","docker","images_load"},call("load_images")).leaf=true - entry({"admin","docker","images_import"},call("import_images")).leaf=true - entry({"admin","docker","images_get_tags"},call("get_image_tags")).leaf=true - entry({"admin","docker","images_tag"},call("tag_image")).leaf=true - entry({"admin","docker","images_untag"},call("untag_image")).leaf=true - entry({"admin","docker","confirm"},call("action_confirm")).leaf=true + entry({"admin", "docker", "newcontainer"}, form("dockerman/newcontainer")).leaf=true + entry({"admin", "docker", "newnetwork"}, form("dockerman/newnetwork")).leaf=true + entry({"admin", "docker", "container"}, form("dockerman/container")).leaf=true + + entry({"admin", "docker", "container_stats"}, call("action_get_container_stats")).leaf=true + entry({"admin", "docker", "container_get_archive"}, call("download_archive")).leaf=true + entry({"admin", "docker", "container_put_archive"}, call("upload_archive")).leaf=true + entry({"admin", "docker", "images_save"}, call("save_images")).leaf=true + entry({"admin", "docker", "images_load"}, call("load_images")).leaf=true + entry({"admin", "docker", "images_import"}, call("import_images")).leaf=true + entry({"admin", "docker", "images_get_tags"}, call("get_image_tags")).leaf=true + entry({"admin", "docker", "images_tag"}, call("tag_image")).leaf=true + entry({"admin", "docker", "images_untag"}, call("untag_image")).leaf=true + entry({"admin", "docker", "confirm"}, call("action_confirm")).leaf=true end function action_events() - local logs = "" - local dk = docker.new() - local query ={} - query["until"] = os.time() - local events = dk:events({query = query}) - if events.code == 200 then - for _, v in ipairs(events.body) do - if v and v.Type == "container" then - logs = (logs ~= "" and (logs .. "\n") or logs) .. "[" .. os.date("%Y-%m-%d %H:%M:%S", v.time) .."] "..v.Type.. " " .. (v.Action or "null") .. " Container ID:".. (v.Actor.ID or "null") .. " Container Name:" .. (v.Actor.Attributes.name or "null") - elseif v.Type == "network" then - logs = (logs ~= "" and (logs .. "\n") or logs) .. "[" .. os.date("%Y-%m-%d %H:%M:%S", v.time) .."] "..v.Type.. " " .. v.Action .. " Container ID:"..( v.Actor.Attributes.container or "null" ) .. " Network Name:" .. (v.Actor.Attributes.name or "null") .. " Network type:".. v.Actor.Attributes.type or "" - elseif v.Type == "image" then - logs = (logs ~= "" and (logs .. "\n") or logs) .. "[" .. os.date("%Y-%m-%d %H:%M:%S", v.time) .."] "..v.Type.. " " .. v.Action .. " Image:".. (v.Actor.ID or "null").. " Image Name:" .. (v.Actor.Attributes.name or "null") - end - end - end - luci.template.render("dockerman/logs", {self={syslog = logs, title="Events"}}) + local logs = "" + local query ={} + + local dk = docker.new() + query["until"] = os.time() + local events = dk:events({query = query}) + + if events.code == 200 then + for _, v in ipairs(events.body) do + if v and v.Type == "container" then + logs = (logs ~= "" and (logs .. "\n") or logs) .. "[" .. os.date("%Y-%m-%d %H:%M:%S", v.time) .."] "..v.Type.. " " .. (v.Action or "null") .. " Container ID:".. (v.Actor.ID or "null") .. " Container Name:" .. (v.Actor.Attributes.name or "null") + elseif v.Type == "network" then + logs = (logs ~= "" and (logs .. "\n") or logs) .. "[" .. os.date("%Y-%m-%d %H:%M:%S", v.time) .."] "..v.Type.. " " .. v.Action .. " Container ID:"..( v.Actor.Attributes.container or "null" ) .. " Network Name:" .. (v.Actor.Attributes.name or "null") .. " Network type:".. v.Actor.Attributes.type or "" + elseif v.Type == "image" then + logs = (logs ~= "" and (logs .. "\n") or logs) .. "[" .. os.date("%Y-%m-%d %H:%M:%S", v.time) .."] "..v.Type.. " " .. v.Action .. " Image:".. (v.Actor.ID or "null").. " Image Name:" .. (v.Actor.Attributes.name or "null") + end + end + end + + luci.template.render("dockerman/logs", {self={syslog = logs, title="Events"}}) end local calculate_cpu_percent = function(d) - if type(d) ~= "table" then return end - cpu_count = tonumber(d["cpu_stats"]["online_cpus"]) - cpu_percent = 0.0 - cpu_delta = tonumber(d["cpu_stats"]["cpu_usage"]["total_usage"]) - tonumber(d["precpu_stats"]["cpu_usage"]["total_usage"]) - system_delta = tonumber(d["cpu_stats"]["system_cpu_usage"]) - tonumber(d["precpu_stats"]["system_cpu_usage"]) - if system_delta > 0.0 then - cpu_percent = string.format("%.2f", cpu_delta / system_delta * 100.0 * cpu_count) - end - -- return cpu_percent .. "%" - return cpu_percent + if type(d) ~= "table" then + return + end + + local cpu_count = tonumber(d["cpu_stats"]["online_cpus"]) + local cpu_percent = 0.0 + local cpu_delta = tonumber(d["cpu_stats"]["cpu_usage"]["total_usage"]) - tonumber(d["precpu_stats"]["cpu_usage"]["total_usage"]) + local system_delta = tonumber(d["cpu_stats"]["system_cpu_usage"]) - tonumber(d["precpu_stats"]["system_cpu_usage"]) + if system_delta > 0.0 then + cpu_percent = string.format("%.2f", cpu_delta / system_delta * 100.0 * cpu_count) + end + + return cpu_percent end local get_memory = function(d) - if type(d) ~= "table" then return end - -- local limit = string.format("%.2f", tonumber(d["memory_stats"]["limit"]) / 1024 / 1024) - -- local usage = string.format("%.2f", (tonumber(d["memory_stats"]["usage"]) - tonumber(d["memory_stats"]["stats"]["total_cache"])) / 1024 / 1024) - -- return usage .. "MB / " .. limit.. "MB" - local limit =tonumber(d["memory_stats"]["limit"]) - local usage = tonumber(d["memory_stats"]["usage"]) - tonumber(d["memory_stats"]["stats"]["total_cache"]) - return usage, limit + if type(d) ~= "table" then + return + end + + local limit =tonumber(d["memory_stats"]["limit"]) + local usage = tonumber(d["memory_stats"]["usage"]) - tonumber(d["memory_stats"]["stats"]["total_cache"]) + + return usage, limit end local get_rx_tx = function(d) - if type(d) ~="table" then return end - -- local data - -- if type(d["networks"]) == "table" then - -- for e, v in pairs(d["networks"]) do - -- data = (data and (data .. "<br>") or "") .. e .. " Total Tx:" .. string.format("%.2f",(tonumber(v.tx_bytes)/1024/1024)) .. "MB Total Rx: ".. string.format("%.2f",(tonumber(v.rx_bytes)/1024/1024)) .. "MB" - -- end - -- end - local data = {} - if type(d["networks"]) == "table" then - for e, v in pairs(d["networks"]) do - data[e] = { - bw_tx = tonumber(v.tx_bytes), - bw_rx = tonumber(v.rx_bytes) - } - end - end - return data + if type(d) ~="table" then + return + end + + local data = {} + if type(d["networks"]) == "table" then + for e, v in pairs(d["networks"]) do + data[e] = { + bw_tx = tonumber(v.tx_bytes), + bw_rx = tonumber(v.rx_bytes) + } + end + end + + return data end function action_get_container_stats(container_id) - if container_id then - local dk = docker.new() - local response = dk.containers:inspect({id = container_id}) - if response.code == 200 and response.body.State.Running then - response = dk.containers:stats({id = container_id, query = {stream = false}}) - if response.code == 200 then - local container_stats = response.body - local cpu_percent = calculate_cpu_percent(container_stats) - local mem_useage, mem_limit = get_memory(container_stats) - local bw_rxtx = get_rx_tx(container_stats) - luci.http.status(response.code, response.body.message) - luci.http.prepare_content("application/json") - luci.http.write_json({ - cpu_percent = cpu_percent, - memory = { - mem_useage = mem_useage, - mem_limit = mem_limit - }, - bw_rxtx = bw_rxtx - }) - else - luci.http.status(response.code, response.body.message) - luci.http.prepare_content("text/plain") - luci.http.write(response.body.message) - end - else - if response.code == 200 then - luci.http.status(500, "container "..container_id.." not running") - luci.http.prepare_content("text/plain") - luci.http.write("Container "..container_id.." not running") - else - luci.http.status(response.code, response.body.message) - luci.http.prepare_content("text/plain") - luci.http.write(response.body.message) - end - end - else - luci.http.status(404, "No container name or id") - luci.http.prepare_content("text/plain") - luci.http.write("No container name or id") - end + if container_id then + local dk = docker.new() + local response = dk.containers:inspect({id = container_id}) + if response.code == 200 and response.body.State.Running then + response = dk.containers:stats({id = container_id, query = {stream = false}}) + if response.code == 200 then + local container_stats = response.body + local cpu_percent = calculate_cpu_percent(container_stats) + local mem_useage, mem_limit = get_memory(container_stats) + local bw_rxtx = get_rx_tx(container_stats) + luci.http.status(response.code, response.body.message) + luci.http.prepare_content("application/json") + luci.http.write_json({ + cpu_percent = cpu_percent, + memory = { + mem_useage = mem_useage, + mem_limit = mem_limit + }, + bw_rxtx = bw_rxtx + }) + else + luci.http.status(response.code, response.body.message) + luci.http.prepare_content("text/plain") + luci.http.write(response.body.message) + end + else + if response.code == 200 then + luci.http.status(500, "container "..container_id.." not running") + luci.http.prepare_content("text/plain") + luci.http.write("Container "..container_id.." not running") + else + luci.http.status(response.code, response.body.message) + luci.http.prepare_content("text/plain") + luci.http.write(response.body.message) + end + end + else + luci.http.status(404, "No container name or id") + luci.http.prepare_content("text/plain") + luci.http.write("No container name or id") + end end function action_confirm() - local data = docker:read_status() - if data then - data = data:gsub("\n","<br>"):gsub(" "," ") - code = 202 - msg = data - else - code = 200 - msg = "finish" - data = "finish" - end - luci.http.status(code, msg) - luci.http.prepare_content("application/json") - luci.http.write_json({info = data}) + local data = docker:read_status() + if data then + data = data:gsub("\n","<br>"):gsub(" "," ") + code = 202 + msg = data + else + code = 200 + msg = "finish" + data = "finish" + end + + luci.http.status(code, msg) + luci.http.prepare_content("application/json") + luci.http.write_json({info = data}) end function download_archive() - local id = luci.http.formvalue("id") - local path = luci.http.formvalue("path") - local dk = docker.new() - local first - - local cb = function(res, chunk) - if res.code == 200 then - if not first then - first = true - luci.http.header('Content-Disposition', 'inline; filename="archive.tar"') - luci.http.header('Content-Type', 'application\/x-tar') - end - luci.ltn12.pump.all(chunk, luci.http.write) - else - if not first then - first = true - luci.http.prepare_content("text/plain") - end - luci.ltn12.pump.all(chunk, luci.http.write) - end - end - - local res = dk.containers:get_archive({id = id, query = {path = path}}, cb) + local id = luci.http.formvalue("id") + local path = luci.http.formvalue("path") + local dk = docker.new() + local first + + local cb = function(res, chunk) + if res.code == 200 then + if not first then + first = true + luci.http.header('Content-Disposition', 'inline; filename="archive.tar"') + luci.http.header('Content-Type', 'application\/x-tar') + end + luci.ltn12.pump.all(chunk, luci.http.write) + else + if not first then + first = true + luci.http.prepare_content("text/plain") + end + luci.ltn12.pump.all(chunk, luci.http.write) + end + end + + local res = dk.containers:get_archive({ + id = id, + query = { + path = path + } + }, cb) end function upload_archive(container_id) - local path = luci.http.formvalue("upload-path") - local dk = docker.new() - local ltn12 = require "luci.ltn12" - - local rec_send = function(sinkout) - luci.http.setfilehandler(function (meta, chunk, eof) - if chunk then - ltn12.pump.step(ltn12.source.string(chunk), sinkout) - end - end) - end - - local res = dk.containers:put_archive({id = container_id, query = {path = path}, body = rec_send}) - local msg = res and res.body and res.body.message or nil - luci.http.status(res.code, msg) - luci.http.prepare_content("application/json") - luci.http.write_json({message = msg}) + local path = luci.http.formvalue("upload-path") + local dk = docker.new() + local ltn12 = require "luci.ltn12" + + local rec_send = function(sinkout) + luci.http.setfilehandler(function (meta, chunk, eof) + if chunk then + ltn12.pump.step(ltn12.source.string(chunk), sinkout) + end + end) + end + + local res = dk.containers:put_archive({ + id = container_id, + query = { + path = path + }, + body = rec_send + }) + + local msg = res and res.body and res.body.message or nil + luci.http.status(res.code, msg) + luci.http.prepare_content("application/json") + luci.http.write_json({message = msg}) end function save_images(container_id) - local names = luci.http.formvalue("names") - local dk = docker.new() - local first - - local cb = function(res, chunk) - if res.code == 200 then - if not first then - first = true - luci.http.status(res.code, res.message) - luci.http.header('Content-Disposition', 'inline; filename="images.tar"') - luci.http.header('Content-Type', 'application\/x-tar') - end - luci.ltn12.pump.all(chunk, luci.http.write) - else - if not first then - first = true - luci.http.prepare_content("text/plain") - end - luci.ltn12.pump.all(chunk, luci.http.write) - end - end - docker:write_status("Images: saving" .. " " .. container_id .. "...") - local res = dk.images:get({id = container_id, query = {names = names}}, cb) - docker:clear_status() - local msg = res and res.body and res.body.message or nil - luci.http.status(res.code, msg) - luci.http.prepare_content("application/json") - luci.http.write_json({message = msg}) + local names = luci.http.formvalue("names") + local dk = docker.new() + local first + + local cb = function(res, chunk) + if res.code == 200 then + if not first then + first = true + luci.http.status(res.code, res.message) + luci.http.header('Content-Disposition', 'inline; filename="images.tar"') + luci.http.header('Content-Type', 'application\/x-tar') + end + luci.ltn12.pump.all(chunk, luci.http.write) + else + if not first then + first = true + luci.http.prepare_content("text/plain") + end + luci.ltn12.pump.all(chunk, luci.http.write) + end + end + + docker:write_status("Images: saving" .. " " .. container_id .. "...") + local res = dk.images:get({ + id = container_id, + query = { + names = names + } + }, cb) + docker:clear_status() + + local msg = res and res.body and res.body.message or nil + luci.http.status(res.code, msg) + luci.http.prepare_content("application/json") + luci.http.write_json({message = msg}) end function load_images() - local path = luci.http.formvalue("upload-path") - local dk = docker.new() - local ltn12 = require "luci.ltn12" - - local rec_send = function(sinkout) - luci.http.setfilehandler(function (meta, chunk, eof) - if chunk then - ltn12.pump.step(ltn12.source.string(chunk), sinkout) - end - end) - end - - docker:write_status("Images: loading...") - local res = dk.images:load({body = rec_send}) - -- res.body = {"stream":"Loaded image ID: sha256:1399d3d81f80d68832e85ed6ba5f94436ca17966539ba715f661bd36f3caf08f\n"} - local msg = res and res.body and ( res.body.message or res.body.stream or res.body.error)or nil - if res.code == 200 and msg and msg:match("Loaded image ID") then - docker:clear_status() - luci.http.status(res.code, msg) - else - docker:append_status("code:" .. res.code.." ".. msg) - luci.http.status(300, msg) - end - luci.http.prepare_content("application/json") - luci.http.write_json({message = msg}) + local path = luci.http.formvalue("upload-path") + local dk = docker.new() + local ltn12 = require "luci.ltn12" + + local rec_send = function(sinkout) + luci.http.setfilehandler(function (meta, chunk, eof) + if chunk then + ltn12.pump.step(ltn12.source.string(chunk), sinkout) + end + end) + end + + docker:write_status("Images: loading...") + local res = dk.images:load({body = rec_send}) + local msg = res and res.body and ( res.body.message or res.body.stream or res.body.error ) or nil + if res.code == 200 and msg and msg:match("Loaded image ID") then + docker:clear_status() + luci.http.status(res.code, msg) + else + docker:append_status("code:" .. res.code.." ".. msg) + luci.http.status(300, msg) + end + + luci.http.prepare_content("application/json") + luci.http.write_json({message = msg}) end function import_images() - local src = luci.http.formvalue("src") - local itag = luci.http.formvalue("tag") - local dk = docker.new() - local ltn12 = require "luci.ltn12" - local rec_send = function(sinkout) - luci.http.setfilehandler(function (meta, chunk, eof) - if chunk then - ltn12.pump.step(ltn12.source.string(chunk), sinkout) - end - end) - end - docker:write_status("Images: importing".. " ".. itag .."...\n") - local repo = itag and itag:match("^([^:]+)") - local tag = itag and itag:match("^[^:]-:([^:]+)") - local res = dk.images:create({query = {fromSrc = src or "-", repo = repo or nil, tag = tag or nil }, body = not src and rec_send or nil}, docker.import_image_show_status_cb) - local msg = res and res.body and ( res.body.message )or nil - if not msg and #res.body == 0 then - -- res.body = {"status":"sha256:d5304b58e2d8cc0a2fd640c05cec1bd4d1229a604ac0dd2909f13b2b47a29285"} - msg = res.body.status or res.body.error - elseif not msg and #res.body >= 1 then - -- res.body = [...{"status":"sha256:d5304b58e2d8cc0a2fd640c05cec1bd4d1229a604ac0dd2909f13b2b47a29285"}] - msg = res.body[#res.body].status or res.body[#res.body].error - end - if res.code == 200 and msg and msg:match("sha256:") then - docker:clear_status() - else - docker:append_status("code:" .. res.code.." ".. msg) - end - luci.http.status(res.code, msg) - luci.http.prepare_content("application/json") - luci.http.write_json({message = msg}) + local src = luci.http.formvalue("src") + local itag = luci.http.formvalue("tag") + local dk = docker.new() + local ltn12 = require "luci.ltn12" + + local rec_send = function(sinkout) + luci.http.setfilehandler(function (meta, chunk, eof) + if chunk then + ltn12.pump.step(ltn12.source.string(chunk), sinkout) + end + end) + end + + docker:write_status("Images: importing".. " ".. itag .."...\n") + local repo = itag and itag:match("^([^:]+)") + local tag = itag and itag:match("^[^:]-:([^:]+)") + local res = dk.images:create({ + query = { + fromSrc = src or "-", + repo = repo or nil, + tag = tag or nil + }, + body = not src and rec_send or nil + }, docker.import_image_show_status_cb) + + local msg = res and res.body and ( res.body.message )or nil + if not msg and #res.body == 0 then + msg = res.body.status or res.body.error + elseif not msg and #res.body >= 1 then + msg = res.body[#res.body].status or res.body[#res.body].error + end + + if res.code == 200 and msg and msg:match("sha256:") then + docker:clear_status() + else + docker:append_status("code:" .. res.code.." ".. msg) + end + + luci.http.status(res.code, msg) + luci.http.prepare_content("application/json") + luci.http.write_json({message = msg}) end function get_image_tags(image_id) - if not image_id then - luci.http.status(400, "no image id") - luci.http.prepare_content("application/json") - luci.http.write_json({message = "no image id"}) - return - end - local dk = docker.new() - local res = dk.images:inspect({id = image_id}) - local msg = res and res.body and res.body.message or nil - luci.http.status(res.code, msg) - luci.http.prepare_content("application/json") - if res.code == 200 then - local tags = res.body.RepoTags - luci.http.write_json({tags = tags}) - else - local msg = res and res.body and res.body.message or nil - luci.http.write_json({message = msg}) - end + if not image_id then + luci.http.status(400, "no image id") + luci.http.prepare_content("application/json") + luci.http.write_json({message = "no image id"}) + return + end + + local dk = docker.new() + local res = dk.images:inspect({ + id = image_id + }) + local msg = res and res.body and res.body.message or nil + luci.http.status(res.code, msg) + luci.http.prepare_content("application/json") + + if res.code == 200 then + local tags = res.body.RepoTags + luci.http.write_json({tags = tags}) + else + local msg = res and res.body and res.body.message or nil + luci.http.write_json({message = msg}) + end end function tag_image(image_id) - local src = luci.http.formvalue("tag") - local image_id = image_id or luci.http.formvalue("id") - if type(src) ~= "string" or not image_id then - luci.http.status(400, "no image id or tag") - luci.http.prepare_content("application/json") - luci.http.write_json({message = "no image id or tag"}) - return - end - local repo = src:match("^([^:]+)") - local tag = src:match("^[^:]-:([^:]+)") - local dk = docker.new() - local res = dk.images:tag({id = image_id, query={repo=repo, tag=tag}}) - local msg = res and res.body and res.body.message or nil - luci.http.status(res.code, msg) - luci.http.prepare_content("application/json") - if res.code == 201 then - local tags = res.body.RepoTags - luci.http.write_json({tags = tags}) - else - local msg = res and res.body and res.body.message or nil - luci.http.write_json({message = msg}) - end + local src = luci.http.formvalue("tag") + local image_id = image_id or luci.http.formvalue("id") + + if type(src) ~= "string" or not image_id then + luci.http.status(400, "no image id or tag") + luci.http.prepare_content("application/json") + luci.http.write_json({message = "no image id or tag"}) + return + end + + local repo = src:match("^([^:]+)") + local tag = src:match("^[^:]-:([^:]+)") + local dk = docker.new() + local res = dk.images:tag({ + id = image_id, + query={ + repo=repo, + tag=tag + } + }) + local msg = res and res.body and res.body.message or nil + luci.http.status(res.code, msg) + luci.http.prepare_content("application/json") + + if res.code == 201 then + local tags = res.body.RepoTags + luci.http.write_json({tags = tags}) + else + local msg = res and res.body and res.body.message or nil + luci.http.write_json({message = msg}) + end end function untag_image(tag) - local tag = tag or luci.http.formvalue("tag") - if not tag then - luci.http.status(400, "no tag name") - luci.http.prepare_content("application/json") - luci.http.write_json({message = "no tag name"}) - return - end - local dk = docker.new() - local res = dk.images:inspect({name = tag}) - if res.code == 200 then - local tags = res.body.RepoTags - if #tags > 1 then - local r = dk.images:remove({name = tag}) - local msg = r and r.body and r.body.message or nil - luci.http.status(r.code, msg) - luci.http.prepare_content("application/json") - luci.http.write_json({message = msg}) - else - luci.http.status(500, "Cannot remove the last tag") - luci.http.prepare_content("application/json") - luci.http.write_json({message = "Cannot remove the last tag"}) - end - else - local msg = res and res.body and res.body.message or nil - luci.http.status(res.code, msg) - luci.http.prepare_content("application/json") - luci.http.write_json({message = msg}) - end + local tag = tag or luci.http.formvalue("tag") + + if not tag then + luci.http.status(400, "no tag name") + luci.http.prepare_content("application/json") + luci.http.write_json({message = "no tag name"}) + return + end + + local dk = docker.new() + local res = dk.images:inspect({name = tag}) + + if res.code == 200 then + local tags = res.body.RepoTags + if #tags > 1 then + local r = dk.images:remove({name = tag}) + local msg = r and r.body and r.body.message or nil + luci.http.status(r.code, msg) + luci.http.prepare_content("application/json") + luci.http.write_json({message = msg}) + else + luci.http.status(500, "Cannot remove the last tag") + luci.http.prepare_content("application/json") + luci.http.write_json({message = "Cannot remove the last tag"}) + end + else + local msg = res and res.body and res.body.message or nil + luci.http.status(res.code, msg) + luci.http.prepare_content("application/json") + luci.http.write_json({message = msg}) + end end diff --git a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua index 7c0c969336..9ea006396a 100644 --- a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua +++ b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/container.lua @@ -4,585 +4,778 @@ Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman> ]]-- require "luci.util" + local docker = require "luci.model.docker" local dk = docker.new() + container_id = arg[1] local action = arg[2] or "info" -local images, networks, container_info -if not container_id then return end -local res = dk.containers:inspect({id = container_id}) -if res.code < 300 then container_info = res.body else return end +local m, s, o +local images, networks, container_info, res + +if not container_id then + return +end + +res = dk.containers:inspect({id = container_id}) +if res.code < 300 then + container_info = res.body +else + return +end + res = dk.networks:list() -if res.code < 300 then networks = res.body else return end +if res.code < 300 then + networks = res.body +else + return +end local get_ports = function(d) - local data - if d.HostConfig and d.HostConfig.PortBindings then - for inter, out in pairs(d.HostConfig.PortBindings) do - data = (data and (data .. "<br>") or "") .. out[1]["HostPort"] .. ":" .. inter - end - end - return data + local data + + if d.HostConfig and d.HostConfig.PortBindings then + for inter, out in pairs(d.HostConfig.PortBindings) do + data = (data and (data .. "<br>") or "") .. out[1]["HostPort"] .. ":" .. inter + end + end + + return data end local get_env = function(d) - local data - if d.Config and d.Config.Env then - for _,v in ipairs(d.Config.Env) do - data = (data and (data .. "<br>") or "") .. v - end - end - return data + local data + + if d.Config and d.Config.Env then + for _,v in ipairs(d.Config.Env) do + data = (data and (data .. "<br>") or "") .. v + end + end + + return data end local get_command = function(d) - local data - if d.Config and d.Config.Cmd then - for _,v in ipairs(d.Config.Cmd) do - data = (data and (data .. " ") or "") .. v - end - end - return data + local data + + if d.Config and d.Config.Cmd then + for _,v in ipairs(d.Config.Cmd) do + data = (data and (data .. " ") or "") .. v + end + end + + return data end local get_mounts = function(d) - local data - if d.Mounts then - for _,v in ipairs(d.Mounts) do - local v_sorce_d, v_dest_d - local v_sorce = "" - local v_dest = "" - for v_sorce_d in v["Source"]:gmatch('[^/]+') do - if v_sorce_d and #v_sorce_d > 12 then - v_sorce = v_sorce .. "/" .. v_sorce_d:sub(1,12) .. "..." - else - v_sorce = v_sorce .."/".. v_sorce_d - end - end - for v_dest_d in v["Destination"]:gmatch('[^/]+') do - if v_dest_d and #v_dest_d > 12 then - v_dest = v_dest .. "/" .. v_dest_d:sub(1,12) .. "..." - else - v_dest = v_dest .."/".. v_dest_d - end - end - data = (data and (data .. "<br>") or "") .. v_sorce .. ":" .. v["Destination"] .. (v["Mode"] ~= "" and (":" .. v["Mode"]) or "") - end - end - return data + local data + + if d.Mounts then + for _,v in ipairs(d.Mounts) do + local v_sorce_d, v_dest_d + local v_sorce = "" + local v_dest = "" + for v_sorce_d in v["Source"]:gmatch('[^/]+') do + if v_sorce_d and #v_sorce_d > 12 then + v_sorce = v_sorce .. "/" .. v_sorce_d:sub(1,12) .. "..." + else + v_sorce = v_sorce .."/".. v_sorce_d + end + end + for v_dest_d in v["Destination"]:gmatch('[^/]+') do + if v_dest_d and #v_dest_d > 12 then + v_dest = v_dest .. "/" .. v_dest_d:sub(1,12) .. "..." + else + v_dest = v_dest .."/".. v_dest_d + end + end + data = (data and (data .. "<br>") or "") .. v_sorce .. ":" .. v["Destination"] .. (v["Mode"] ~= "" and (":" .. v["Mode"]) or "") + end + end + + return data end local get_device = function(d) - local data - if d.HostConfig and d.HostConfig.Devices then - for _,v in ipairs(d.HostConfig.Devices) do - data = (data and (data .. "<br>") or "") .. v["PathOnHost"] .. ":" .. v["PathInContainer"] .. (v["CgroupPermissions"] ~= "" and (":" .. v["CgroupPermissions"]) or "") - end - end - return data + local data + + if d.HostConfig and d.HostConfig.Devices then + for _,v in ipairs(d.HostConfig.Devices) do + data = (data and (data .. "<br>") or "") .. v["PathOnHost"] .. ":" .. v["PathInContainer"] .. (v["CgroupPermissions"] ~= "" and (":" .. v["CgroupPermissions"]) or "") + end + end + + return data end local get_links = function(d) - local data - if d.HostConfig and d.HostConfig.Links then - for _,v in ipairs(d.HostConfig.Links) do - data = (data and (data .. "<br>") or "") .. v - end - end - return data + local data + + if d.HostConfig and d.HostConfig.Links then + for _,v in ipairs(d.HostConfig.Links) do + data = (data and (data .. "<br>") or "") .. v + end + end + + return data end local get_tmpfs = function(d) - local data - if d.HostConfig and d.HostConfig.Tmpfs then - for k, v in pairs(d.HostConfig.Tmpfs) do - data = (data and (data .. "<br>") or "") .. k .. (v~="" and ":" or "")..v - end - end - return data + local data + + if d.HostConfig and d.HostConfig.Tmpfs then + for k, v in pairs(d.HostConfig.Tmpfs) do + data = (data and (data .. "<br>") or "") .. k .. (v~="" and ":" or "")..v + end + end + + return data end local get_dns = function(d) - local data - if d.HostConfig and d.HostConfig.Dns then - for _, v in ipairs(d.HostConfig.Dns) do - data = (data and (data .. "<br>") or "") .. v - end - end - return data + local data + + if d.HostConfig and d.HostConfig.Dns then + for _, v in ipairs(d.HostConfig.Dns) do + data = (data and (data .. "<br>") or "") .. v + end + end + + return data end local get_sysctl = function(d) - local data - if d.HostConfig and d.HostConfig.Sysctls then - for k, v in pairs(d.HostConfig.Sysctls) do - data = (data and (data .. "<br>") or "") .. k..":"..v - end - end - return data + local data + + if d.HostConfig and d.HostConfig.Sysctls then + for k, v in pairs(d.HostConfig.Sysctls) do + data = (data and (data .. "<br>") or "") .. k..":"..v + end + end + + return data end local get_networks = function(d) - local data={} - if d.NetworkSettings and d.NetworkSettings.Networks and type(d.NetworkSettings.Networks) == "table" then - for k,v in pairs(d.NetworkSettings.Networks) do - data[k] = v.IPAddress or "" - end - end - return data + local data={} + + if d.NetworkSettings and d.NetworkSettings.Networks and type(d.NetworkSettings.Networks) == "table" then + for k,v in pairs(d.NetworkSettings.Networks) do + data[k] = v.IPAddress or "" + end + end + + return data end local start_stop_remove = function(m, cmd) - docker:clear_status() - docker:append_status("Containers: " .. cmd .. " " .. container_id .. "...") - local res - if cmd ~= "upgrade" then - res = dk.containers[cmd](dk, {id = container_id}) - else - res = dk.containers_upgrade(dk, {id = container_id}) - end - if res and res.code >= 300 then - docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message)) - luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id)) - else - docker:clear_status() - if cmd ~= "remove" and cmd ~= "upgrade" then - luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id)) - else - luci.http.redirect(luci.dispatcher.build_url("admin/docker/containers")) - end - end + local res + + docker:clear_status() + docker:append_status("Containers: " .. cmd .. " " .. container_id .. "...") + + if cmd ~= "upgrade" then + res = dk.containers[cmd](dk, {id = container_id}) + else + res = dk.containers_upgrade(dk, {id = container_id}) + end + + if res and res.code >= 300 then + docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message)) + luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id)) + else + docker:clear_status() + if cmd ~= "remove" and cmd ~= "upgrade" then + luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id)) + else + luci.http.redirect(luci.dispatcher.build_url("admin/docker/containers")) + end + end end m=SimpleForm("docker", container_info.Name:sub(2), translate("Docker Container") ) m.redirect = luci.dispatcher.build_url("admin/docker/containers") --- m:append(Template("dockerman/container")) -docker_status = m:section(SimpleSection) -docker_status.template = "dockerman/apply_widget" -docker_status.err=docker:read_status() -docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" "," ") -if docker_status.err then docker:clear_status() end - - -action_section = m:section(Table,{{}}) -action_section.notitle=true -action_section.rowcolors=false -action_section.template = "cbi/nullsection" - -btnstart=action_section:option(Button, "_start") -btnstart.template = "dockerman/cbi/inlinebutton" -btnstart.inputtitle=translate("Start") -btnstart.inputstyle = "apply" -btnstart.forcewrite = true -btnrestart=action_section:option(Button, "_restart") -btnrestart.template = "dockerman/cbi/inlinebutton" -btnrestart.inputtitle=translate("Restart") -btnrestart.inputstyle = "reload" -btnrestart.forcewrite = true -btnstop=action_section:option(Button, "_stop") -btnstop.template = "dockerman/cbi/inlinebutton" -btnstop.inputtitle=translate("Stop") -btnstop.inputstyle = "reset" -btnstop.forcewrite = true -btnkill=action_section:option(Button, "_kill") -btnkill.template = "dockerman/cbi/inlinebutton" -btnkill.inputtitle=translate("Kill") -btnkill.inputstyle = "reset" -btnkill.forcewrite = true -btnupgrade=action_section:option(Button, "_upgrade") -btnupgrade.template = "dockerman/cbi/inlinebutton" -btnupgrade.inputtitle=translate("Upgrade") -btnupgrade.inputstyle = "reload" -btnstop.forcewrite = true -btnduplicate=action_section:option(Button, "_duplicate") -btnduplicate.template = "dockerman/cbi/inlinebutton" -btnduplicate.inputtitle=translate("Duplicate/Edit") -btnduplicate.inputstyle = "add" -btnstop.forcewrite = true -btnremove=action_section:option(Button, "_remove") -btnremove.template = "dockerman/cbi/inlinebutton" -btnremove.inputtitle=translate("Remove") -btnremove.inputstyle = "remove" -btnremove.forcewrite = true - -btnstart.write = function(self, section) - start_stop_remove(m,"start") + +s = m:section(SimpleSection) +s.template = "dockerman/apply_widget" +s.err=docker:read_status() +s.err=s.err and s.err:gsub("\n","<br>"):gsub(" "," ") +if s.err then + docker:clear_status() +end + +s = m:section(Table,{{}}) +s.notitle=true +s.rowcolors=false +s.template = "cbi/nullsection" + +o = s:option(Button, "_start") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle=translate("Start") +o.inputstyle = "apply" +o.forcewrite = true +o.write = function(self, section) + start_stop_remove(m,"start") end -btnrestart.write = function(self, section) - start_stop_remove(m,"restart") + +o = s:option(Button, "_restart") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle=translate("Restart") +o.inputstyle = "reload" +o.forcewrite = true +o.write = function(self, section) + start_stop_remove(m,"restart") end -btnupgrade.write = function(self, section) - start_stop_remove(m,"upgrade") + +o = s:option(Button, "_stop") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle=translate("Stop") +o.inputstyle = "reset" +o.forcewrite = true +o.write = function(self, section) + start_stop_remove(m,"stop") end -btnremove.write = function(self, section) - start_stop_remove(m,"remove") + +o = s:option(Button, "_kill") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle=translate("Kill") +o.inputstyle = "reset" +o.forcewrite = true +o.write = function(self, section) + start_stop_remove(m,"kill") end -btnstop.write = function(self, section) - start_stop_remove(m,"stop") + +o = s:option(Button, "_upgrade") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle=translate("Upgrade") +o.inputstyle = "reload" +o.forcewrite = true +o.write = function(self, section) + start_stop_remove(m,"upgrade") end -btnkill.write = function(self, section) - start_stop_remove(m,"kill") + +o = s:option(Button, "_duplicate") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle=translate("Duplicate/Edit") +o.inputstyle = "add" +o.forcewrite = true +o.write = function(self, section) + luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer/duplicate/"..container_id)) end -btnduplicate.write = function(self, section) - luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer/duplicate/"..container_id)) + +o = s:option(Button, "_remove") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle=translate("Remove") +o.inputstyle = "remove" +o.forcewrite = true +o.write = function(self, section) + start_stop_remove(m,"remove") end -tab_section = m:section(SimpleSection) -tab_section.template = "dockerman/container" - -if action == "info" then - m.submit = false - m.reset = false - table_info = { - ["01name"] = {_key = translate("Name"), _value = container_info.Name:sub(2) or "-", _button=translate("Update")}, - ["02id"] = {_key = translate("ID"), _value = container_info.Id or "-"}, - ["03image"] = {_key = translate("Image"), _value = container_info.Config.Image .. "<br>" .. container_info.Image}, - ["04status"] = {_key = translate("Status"), _value = container_info.State and container_info.State.Status or "-"}, - ["05created"] = {_key = translate("Created"), _value = container_info.Created or "-"}, - } - table_info["06start"] = container_info.State.Status == "running" and {_key = translate("Start Time"), _value = container_info.State and container_info.State.StartedAt or "-"} or {_key = translate("Finish Time"), _value = container_info.State and container_info.State.FinishedAt or "-"} - table_info["07healthy"] = {_key = translate("Healthy"), _value = container_info.State and container_info.State.Health and container_info.State.Health.Status or "-"} - table_info["08restart"] = {_key = translate("Restart Policy"), _value = container_info.HostConfig and container_info.HostConfig.RestartPolicy and container_info.HostConfig.RestartPolicy.Name or "-", _button=translate("Update")} - table_info["081user"] = {_key = translate("User"), _value = container_info.Config and (container_info.Config.User ~="" and container_info.Config.User or "-") or "-"} - table_info["09mount"] = {_key = translate("Mount/Volume"), _value = get_mounts(container_info) or "-"} - table_info["10cmd"] = {_key = translate("Command"), _value = get_command(container_info) or "-"} - table_info["11env"] = {_key = translate("Env"), _value = get_env(container_info) or "-"} - table_info["12ports"] = {_key = translate("Ports"), _value = get_ports(container_info) or "-"} - table_info["13links"] = {_key = translate("Links"), _value = get_links(container_info) or "-"} - table_info["14device"] = {_key = translate("Device"), _value = get_device(container_info) or "-"} - table_info["15tmpfs"] = {_key = translate("Tmpfs"), _value = get_tmpfs(container_info) or "-"} - table_info["16dns"] = {_key = translate("DNS"), _value = get_dns(container_info) or "-"} - table_info["17sysctl"] = {_key = translate("Sysctl"), _value = get_sysctl(container_info) or "-"} - info_networks = get_networks(container_info) - list_networks = {} - for _, v in ipairs (networks) do - if v.Name then - local parent = v.Options and v.Options.parent or nil - local ip = v.IPAM and v.IPAM.Config and v.IPAM.Config[1] and v.IPAM.Config[1].Subnet or nil - ipv6 = v.IPAM and v.IPAM.Config and v.IPAM.Config[2] and v.IPAM.Config[2].Subnet or nil - local network_name = v.Name .. " | " .. v.Driver .. (parent and (" | " .. parent) or "") .. (ip and (" | " .. ip) or "").. (ipv6 and (" | " .. ipv6) or "") - list_networks[v.Name] = network_name - end - end - - if type(info_networks)== "table" then - for k,v in pairs(info_networks) do - table_info["14network"..k] = { - _key = translate("Network"), _value = k.. (v~="" and (" | ".. v) or ""), _button=translate("Disconnect") - } - list_networks[k]=nil - end - end - - table_info["15connect"] = {_key = translate("Connect Network"), _value = list_networks ,_opts = "", _button=translate("Connect")} - - - d_info = m:section(Table,table_info) - d_info.nodescr=true - d_info.formvalue=function(self, section) - return table_info - end - dv_key = d_info:option(DummyValue, "_key", translate("Info")) - dv_key.width = "20%" - dv_value = d_info:option(ListValue, "_value") - dv_value.render = function(self, section, scope) - if table_info[section]._key == translate("Name") then - self:reset_values() - self.template = "cbi/value" - self.size = 30 - self.keylist = {} - self.vallist = {} - self.default=table_info[section]._value - Value.render(self, section, scope) - elseif table_info[section]._key == translate("Restart Policy") then - self.template = "cbi/lvalue" - self:reset_values() - self.size = nil - self:value("no", "No") - self:value("unless-stopped", "Unless stopped") - self:value("always", "Always") - self:value("on-failure", "On failure") - self.default=table_info[section]._value - ListValue.render(self, section, scope) - elseif table_info[section]._key == translate("Connect Network") then - self.template = "cbi/lvalue" - self:reset_values() - self.size = nil - for k,v in pairs(list_networks) do - if k ~= "host" then - self:value(k,v) - end - end - self.default=table_info[section]._value - ListValue.render(self, section, scope) - else - self:reset_values() - self.rawhtml=true - self.template = "cbi/dvalue" - self.default=table_info[section]._value - DummyValue.render(self, section, scope) - end - end - dv_value.forcewrite = true -- for write function using simpleform - dv_value.write = function(self, section, value) - table_info[section]._value=value - end - dv_value.validate = function(self, value) - return value - end - dv_opts = d_info:option(Value, "_opts") - dv_opts.forcewrite = true -- for write function using simpleform - dv_opts.write = function(self, section, value) - - table_info[section]._opts=value - end - dv_opts.validate = function(self, value) - return value - end - dv_opts.render = function(self, section, scope) - if table_info[section]._key==translate("Connect Network") then - self.template = "cbi/value" - self.keylist = {} - self.vallist = {} - self.placeholder = "10.1.1.254" - self.datatype = "ip4addr" - self.default=table_info[section]._opts - Value.render(self, section, scope) - else - self.rawhtml=true - self.template = "cbi/dvalue" - self.default=table_info[section]._opts - DummyValue.render(self, section, scope) - end - end - btn_update = d_info:option(Button, "_button") - btn_update.forcewrite = true - btn_update.render = function(self, section, scope) - if table_info[section]._button and table_info[section]._value ~= nil then - btn_update.inputtitle=table_info[section]._button - self.template = "cbi/button" - self.inputstyle = "edit" - Button.render(self, section, scope) - else - self.template = "cbi/dvalue" - self.default="" - DummyValue.render(self, section, scope) - end - end - btn_update.write = function(self, section, value) - local res - docker:clear_status() - if section == "01name" then - docker:append_status("Containers: rename " .. container_id .. "...") - local new_name = table_info[section]._value - res = dk.containers:rename({id = container_id, query = {name=new_name}}) - elseif section == "08restart" then - docker:append_status("Containers: update " .. container_id .. "...") - local new_restart = table_info[section]._value - res = dk.containers:update({id = container_id, body = {RestartPolicy = {Name = new_restart}}}) - elseif table_info[section]._key == translate("Network") then - local _,_,leave_network = table_info[section]._value:find("(.-) | .+") - leave_network = leave_network or table_info[section]._value - docker:append_status("Network: disconnect " .. leave_network .. container_id .. "...") - res = dk.networks:disconnect({name = leave_network, body = {Container = container_id}}) - elseif section == "15connect" then - local connect_network = table_info[section]._value - local network_opiton - if connect_network ~= "none" and connect_network ~= "bridge" and connect_network ~= "host" then - network_opiton = table_info[section]._opts ~= "" and { - IPAMConfig={ - IPv4Address=table_info[section]._opts - } - } or nil - end - docker:append_status("Network: connect " .. connect_network .. container_id .. "...") - res = dk.networks:connect({name = connect_network, body = {Container = container_id, EndpointConfig= network_opiton}}) - end - if res and res.code > 300 then - docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message)) - else - docker:clear_status() - end - luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id.."/info")) - end - --- info end +s = m:section(SimpleSection) +s.template = "dockerman/container" + +if action == "info" then + m.submit = false + m.reset = false + table_info = { + ["01name"] = { + _key = translate("Name"), + _value = container_info.Name:sub(2) or "-", + _button=translate("Update") + }, + ["02id"] = { + _key = translate("ID"), + _value = container_info.Id or "-" + }, + ["03image"] = { + _key = translate("Image"), + _value = container_info.Config.Image .. "<br>" .. container_info.Image + }, + ["04status"] = { + _key = translate("Status"), + _value = container_info.State and container_info.State.Status or "-" + }, + ["05created"] = { + _key = translate("Created"), + _value = container_info.Created or "-" + }, + } + + if container_info.State.Status == "running" then + table_info["06start"] = { + _key = translate("Start Time"), + _value = container_info.State and container_info.State.StartedAt or "-" + } + else + table_info["06start"] = { + _key = translate("Finish Time"), + _value = container_info.State and container_info.State.FinishedAt or "-" + } + end + + table_info["07healthy"] = { + _key = translate("Healthy"), + _value = container_info.State and container_info.State.Health and container_info.State.Health.Status or "-" + } + table_info["08restart"] = { + _key = translate("Restart Policy"), + _value = container_info.HostConfig and container_info.HostConfig.RestartPolicy and container_info.HostConfig.RestartPolicy.Name or "-", + _button=translate("Update") + } + table_info["081user"] = { + _key = translate("User"), + _value = container_info.Config and (container_info.Config.User ~="" and container_info.Config.User or "-") or "-" + } + table_info["09mount"] = { + _key = translate("Mount/Volume"), + _value = get_mounts(container_info) or "-" + } + table_info["10cmd"] = { + _key = translate("Command"), + _value = get_command(container_info) or "-" + } + table_info["11env"] = { + _key = translate("Env"), + _value = get_env(container_info) or "-" + } + table_info["12ports"] = { + _key = translate("Ports"), + _value = get_ports(container_info) or "-" + } + table_info["13links"] = { + _key = translate("Links"), + _value = get_links(container_info) or "-" + } + table_info["14device"] = { + _key = translate("Device"), + _value = get_device(container_info) or "-" + } + table_info["15tmpfs"] = { + _key = translate("Tmpfs"), + _value = get_tmpfs(container_info) or "-" + } + table_info["16dns"] = { + _key = translate("DNS"), + _value = get_dns(container_info) or "-" + } + table_info["17sysctl"] = { + _key = translate("Sysctl"), + _value = get_sysctl(container_info) or "-" + } + + info_networks = get_networks(container_info) + list_networks = {} + for _, v in ipairs (networks) do + if v.Name then + local parent = v.Options and v.Options.parent or nil + local ip = v.IPAM and v.IPAM.Config and v.IPAM.Config[1] and v.IPAM.Config[1].Subnet or nil + ipv6 = v.IPAM and v.IPAM.Config and v.IPAM.Config[2] and v.IPAM.Config[2].Subnet or nil + local network_name = v.Name .. " | " .. v.Driver .. (parent and (" | " .. parent) or "") .. (ip and (" | " .. ip) or "").. (ipv6 and (" | " .. ipv6) or "") + list_networks[v.Name] = network_name + end + end + + if type(info_networks)== "table" then + for k,v in pairs(info_networks) do + table_info["14network"..k] = { + _key = translate("Network"), + value = k.. (v~="" and (" | ".. v) or ""), + _button=translate("Disconnect") + } + list_networks[k]=nil + end + end + + table_info["15connect"] = { + _key = translate("Connect Network"), + _value = list_networks ,_opts = "", + _button=translate("Connect") + } + + s = m:section(Table,table_info) + s.nodescr=true + s.formvalue=function(self, section) + return table_info + end + + o = s:option(DummyValue, "_key", translate("Info")) + o.width = "20%" + + o = s:option(ListValue, "_value") + o.render = function(self, section, scope) + if table_info[section]._key == translate("Name") then + self:reset_values() + self.template = "cbi/value" + self.size = 30 + self.keylist = {} + self.vallist = {} + self.default=table_info[section]._value + Value.render(self, section, scope) + elseif table_info[section]._key == translate("Restart Policy") then + self.template = "cbi/lvalue" + self:reset_values() + self.size = nil + self:value("no", "No") + self:value("unless-stopped", "Unless stopped") + self:value("always", "Always") + self:value("on-failure", "On failure") + self.default=table_info[section]._value + ListValue.render(self, section, scope) + elseif table_info[section]._key == translate("Connect Network") then + self.template = "cbi/lvalue" + self:reset_values() + self.size = nil + for k,v in pairs(list_networks) do + if k ~= "host" then + self:value(k,v) + end + end + self.default=table_info[section]._value + ListValue.render(self, section, scope) + else + self:reset_values() + self.rawhtml=true + self.template = "cbi/dvalue" + self.default=table_info[section]._value + DummyValue.render(self, section, scope) + end + end + o.forcewrite = true + o.write = function(self, section, value) + table_info[section]._value=value + end + o.validate = function(self, value) + return value + end + + o = s:option(Value, "_opts") + o.forcewrite = true + o.write = function(self, section, value) + table_info[section]._opts=value + end + o.validate = function(self, value) + return value + end + o.render = function(self, section, scope) + if table_info[section]._key==translate("Connect Network") then + self.template = "cbi/value" + self.keylist = {} + self.vallist = {} + self.placeholder = "10.1.1.254" + self.datatype = "ip4addr" + self.default=table_info[section]._opts + Value.render(self, section, scope) + else + self.rawhtml=true + self.template = "cbi/dvalue" + self.default=table_info[section]._opts + DummyValue.render(self, section, scope) + end + end + + o = s:option(Button, "_button") + o.forcewrite = true + o.render = function(self, section, scope) + if table_info[section]._button and table_info[section]._value ~= nil then + self.inputtitle=table_info[section]._button + self.template = "cbi/button" + self.inputstyle = "edit" + Button.render(self, section, scope) + else + self.template = "cbi/dvalue" + self.default="" + DummyValue.render(self, section, scope) + end + end + o.write = function(self, section, value) + local res + + docker:clear_status() + + if section == "01name" then + docker:append_status("Containers: rename " .. container_id .. "...") + local new_name = table_info[section]._value + res = dk.containers:rename({ + id = container_id, + query = { + name=new_name + } + }) + elseif section == "08restart" then + docker:append_status("Containers: update " .. container_id .. "...") + local new_restart = table_info[section]._value + res = dk.containers:update({ + id = container_id, + body = { + RestartPolicy = { + Name = new_restart + } + } + }) + elseif table_info[section]._key == translate("Network") then + local _,_,leave_network + + _, _, leave_network = table_info[section]._value:find("(.-) | .+") + leave_network = leave_network or table_info[section]._value + docker:append_status("Network: disconnect " .. leave_network .. container_id .. "...") + res = dk.networks:disconnect({ + name = leave_network, + body = { + Container = container_id + } + }) + elseif section == "15connect" then + local connect_network = table_info[section]._value + local network_opiton + if connect_network ~= "none" + and connect_network ~= "bridge" + and connect_network ~= "host" then + + network_opiton = table_info[section]._opts ~= "" and { + IPAMConfig={ + IPv4Address=table_info[section]._opts + } + } or nil + end + docker:append_status("Network: connect " .. connect_network .. container_id .. "...") + res = dk.networks:connect({ + name = connect_network, + body = { + Container = container_id, + EndpointConfig= network_opiton + } + }) + end + + if res and res.code > 300 then + docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message)) + else + docker:clear_status() + end + luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id.."/info")) + end elseif action == "resources" then - local resources_section= m:section(SimpleSection) - d = resources_section:option( Value, "cpus", translate("CPUs"), translate("Number of CPUs. Number is a fractional number. 0.000 means no limit.")) - d.placeholder = "1.5" - d.rmempty = true - d.datatype="ufloat" - d.default = container_info.HostConfig.NanoCpus / (10^9) - - d = resources_section:option(Value, "cpushares", translate("CPU Shares Weight"), translate("CPU shares relative weight, if 0 is set, the system will ignore the value and use the default of 1024.")) - d.placeholder = "1024" - d.rmempty = true - d.datatype="uinteger" - d.default = container_info.HostConfig.CpuShares - - d = resources_section:option(Value, "memory", translate("Memory"), translate("Memory limit (format: <number>[<unit>]). Number is a positive integer. Unit can be one of b, k, m, or g. Minimum is 4M.")) - d.placeholder = "128m" - d.rmempty = true - d.default = container_info.HostConfig.Memory ~=0 and ((container_info.HostConfig.Memory / 1024 /1024) .. "M") or 0 - - d = resources_section:option(Value, "blkioweight", translate("Block IO Weight"), translate("Block IO weight (relative weight) accepts a weight value between 10 and 1000.")) - d.placeholder = "500" - d.rmempty = true - d.datatype="uinteger" - d.default = container_info.HostConfig.BlkioWeight - - m.handle = function(self, state, data) - if state == FORM_VALID then - local memory = data.memory - if memory and memory ~= 0 then - _,_,n,unit = memory:find("([%d%.]+)([%l%u]+)") - if n then - unit = unit and unit:sub(1,1):upper() or "B" - if unit == "M" then - memory = tonumber(n) * 1024 * 1024 - elseif unit == "G" then - memory = tonumber(n) * 1024 * 1024 * 1024 - elseif unit == "K" then - memory = tonumber(n) * 1024 - else - memory = tonumber(n) - end - end - end - request_body = { - BlkioWeight = tonumber(data.blkioweight), - NanoCPUs = tonumber(data.cpus)*10^9, - Memory = tonumber(memory), - CpuShares = tonumber(data.cpushares) - } - docker:write_status("Containers: update " .. container_id .. "...") - local res = dk.containers:update({id = container_id, body = request_body}) - if res and res.code >= 300 then - docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message)) - else - docker:clear_status() - end - luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id.."/resources")) - end - end + s = m:section(SimpleSection) + o = s:option( Value, "cpus", + translate("CPUs"), + translate("Number of CPUs. Number is a fractional number. 0.000 means no limit.")) + o.placeholder = "1.5" + o.rmempty = true + o.datatype="ufloat" + o.default = container_info.HostConfig.NanoCpus / (10^9) + + o = s:option(Value, "cpushares", + translate("CPU Shares Weight"), + translate("CPU shares relative weight, if 0 is set, the system will ignore the value and use the default of 1024.")) + o.placeholder = "1024" + o.rmempty = true + o.datatype="uinteger" + o.default = container_info.HostConfig.CpuShares + + o = s:option(Value, "memory", + translate("Memory"), + translate("Memory limit (format: <number>[<unit>]). Number is a positive integer. Unit can be one of b, k, m, or g. Minimum is 4M.")) + o.placeholder = "128m" + o.rmempty = true + o.default = container_info.HostConfig.Memory ~=0 and ((container_info.HostConfig.Memory / 1024 /1024) .. "M") or 0 + + o = s:option(Value, "blkioweight", + translate("Block IO Weight"), + translate("Block IO weight (relative weight) accepts a weight value between 10 and 1000.")) + o.placeholder = "500" + o.rmempty = true + o.datatype="uinteger" + o.default = container_info.HostConfig.BlkioWeight + + m.handle = function(self, state, data) + if state == FORM_VALID then + local memory = data.memory + if memory and memory ~= 0 then + _,_,n,unit = memory:find("([%d%.]+)([%l%u]+)") + if n then + unit = unit and unit:sub(1,1):upper() or "B" + if unit == "M" then + memory = tonumber(n) * 1024 * 1024 + elseif unit == "G" then + memory = tonumber(n) * 1024 * 1024 * 1024 + elseif unit == "K" then + memory = tonumber(n) * 1024 + else + memory = tonumber(n) + end + end + end + + request_body = { + BlkioWeight = tonumber(data.blkioweight), + NanoCPUs = tonumber(data.cpus)*10^9, + Memory = tonumber(memory), + CpuShares = tonumber(data.cpushares) + } + + docker:write_status("Containers: update " .. container_id .. "...") + local res = dk.containers:update({id = container_id, body = request_body}) + if res and res.code >= 300 then + docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message)) + else + docker:clear_status() + end + luci.http.redirect(luci.dispatcher.build_url("admin/docker/container/"..container_id.."/resources")) + end + end + elseif action == "file" then - local filesection= m:section(SimpleSection) - m.submit = false - m.reset = false - filesection.template = "dockerman/container_file" - filesection.container = container_id + s = m:section(SimpleSection) + s.template = "dockerman/container_file" + s.container = container_id + m.submit = false + m.reset = false elseif action == "inspect" then - local inspectsection= m:section(SimpleSection) - inspectsection.syslog = luci.jsonc.stringify(container_info, true) - inspectsection.title = translate("Container Inspect") - inspectsection.template = "dockerman/logs" - m.submit = false - m.reset = false + s = m:section(SimpleSection) + s.syslog = luci.jsonc.stringify(container_info, true) + s.title = translate("Container Inspect") + s.template = "dockerman/logs" + m.submit = false + m.reset = false elseif action == "logs" then - local logsection= m:section(SimpleSection) - local logs = "" - local query ={ - stdout = 1, - stderr = 1, - tail = 1000 - } - local logs = dk.containers:logs({id = container_id, query = query}) - if logs.code == 200 then - logsection.syslog=logs.body - else - logsection.syslog="Get Logs ERROR\n"..logs.code..": "..logs.body - end - logsection.title=translate("Container Logs") - logsection.template = "dockerman/logs" - m.submit = false - m.reset = false + local logs = "" + local query ={ + stdout = 1, + stderr = 1, + tail = 1000 + } + + s = m:section(SimpleSection) + + logs = dk.containers:logs({id = container_id, query = query}) + if logs.code == 200 then + s.syslog=logs.body + else + s.syslog="Get Logs ERROR\n"..logs.code..": "..logs.body + end + + s.title=translate("Container Logs") + s.template = "dockerman/logs" + m.submit = false + m.reset = false elseif action == "console" then - m.submit = false - m.reset = false - local cmd_docker = luci.util.exec("which docker"):match("^.+docker") or nil - local cmd_ttyd = luci.util.exec("which ttyd"):match("^.+ttyd") or nil - if cmd_docker and cmd_ttyd and container_info.State.Status == "running" then - local consolesection= m:section(SimpleSection) - local cmd = "/bin/sh" - local uid - local vcommand = consolesection:option(Value, "command", translate("Command")) - vcommand:value("/bin/sh", "/bin/sh") - vcommand:value("/bin/ash", "/bin/ash") - vcommand:value("/bin/bash", "/bin/bash") - vcommand.default = "/bin/sh" - vcommand.forcewrite = true - vcommand.write = function(self, section, value) - cmd = value - end - local vuid = consolesection:option(Value, "uid", translate("UID")) - vuid.forcewrite = true - vuid.write = function(self, section, value) - uid = value - end - local btn_connect = consolesection:option(Button, "connect") - btn_connect.render = function(self, section, scope) - self.inputstyle = "add" - self.title = " " - self.inputtitle = translate("Connect") - Button.render(self, section, scope) - end - btn_connect.write = function(self, section) - local cmd_docker = luci.util.exec("which docker"):match("^.+docker") or nil - local cmd_ttyd = luci.util.exec("which ttyd"):match("^.+ttyd") or nil - if not cmd_docker or not cmd_ttyd or cmd_docker:match("^%s+$") or cmd_ttyd:match("^%s+$") then return end - local kill_ttyd = 'netstat -lnpt | grep ":7682[ \t].*ttyd$" | awk \'{print $NF}\' | awk -F\'/\' \'{print "kill -9 " $1}\' | sh > /dev/null' - luci.util.exec(kill_ttyd) - local hosts - local uci = (require "luci.model.uci").cursor() - local remote = uci:get("dockerman", "local", "remote_endpoint") - local socket_path = (remote == "false" or not remote) and uci:get("dockerman", "local", "socket_path") or nil - local host = (remote == "true") and uci:get("dockerman", "local", "remote_host") or nil - local port = (remote == "true") and uci:get("dockerman", "local", "remote_port") or nil - if remote and host and port then - hosts = host .. ':'.. port - elseif socket_path then - hosts = "unix://" .. socket_path - else - return - end - local start_cmd = cmd_ttyd .. ' -d 2 --once -p 7682 '.. cmd_docker .. ' -H "'.. hosts ..'" exec -it ' .. (uid and uid ~= "" and (" -u ".. uid .. ' ') or "").. container_id .. ' ' .. cmd .. ' &' - os.execute(start_cmd) - local console = consolesection:option(DummyValue, "console") - console.container_id = container_id - console.template = "dockerman/container_console" - end - end + m.submit = false + m.reset = false + local cmd_docker = luci.util.exec("which docker"):match("^.+docker") or nil + local cmd_ttyd = luci.util.exec("which ttyd"):match("^.+ttyd") or nil + + if cmd_docker and cmd_ttyd and container_info.State.Status == "running" then + local cmd = "/bin/sh" + local uid + + s = m:section(SimpleSection) + + o = s:option(Value, "command", translate("Command")) + o:value("/bin/sh", "/bin/sh") + o:value("/bin/ash", "/bin/ash") + o:value("/bin/bash", "/bin/bash") + o.default = "/bin/sh" + o.forcewrite = true + o.write = function(self, section, value) + cmd = value + end + + o = s:option(Value, "uid", translate("UID")) + o.forcewrite = true + o.write = function(self, section, value) + uid = value + end + + o = s:option(Button, "connect") + o.render = function(self, section, scope) + self.inputstyle = "add" + self.title = " " + self.inputtitle = translate("Connect") + Button.render(self, section, scope) + end + o.write = function(self, section) + local cmd_docker = luci.util.exec("which docker"):match("^.+docker") or nil + local cmd_ttyd = luci.util.exec("which ttyd"):match("^.+ttyd") or nil + + if not cmd_docker or not cmd_ttyd or cmd_docker:match("^%s+$") or cmd_ttyd:match("^%s+$")then + return + end + + local kill_ttyd = 'netstat -lnpt | grep ":7682[ \t].*ttyd$" | awk \'{print $NF}\' | awk -F\'/\' \'{print "kill -9 " $1}\' | sh > /dev/null' + luci.util.exec(kill_ttyd) + local hosts + local uci = (require "luci.model.uci").cursor() + local remote = uci:get_bool("dockerd", "globals", "remote_endpoint") + local host = nil + local port = nil + local socket = nil + + if remote then + host = uci:get("dockerd", "globals", "remote_host") or nil + port = uci:get("dockerd", "globals", "remote_port") or nil + else + socket = uci:get("dockerd", "globals", "socket_path") or nil + end + + if remote and host and port then + hosts = host .. ':'.. port + elseif socket_path then + hosts = socket_path + else + return + end + + local start_cmd = cmd_ttyd .. ' -d 2 --once -p 7682 '.. cmd_docker .. ' -H "'.. hosts ..'" exec -it ' .. (uid and uid ~= "" and (" -u ".. uid .. ' ') or "").. container_id .. ' ' .. cmd .. ' &' + os.execute(start_cmd) + + o = s:option(DummyValue, "console") + o.container_id = container_id + o.template = "dockerman/container_console" + end + end elseif action == "stats" then - local response = dk.containers:top({id = container_id, query = {ps_args="-aux"}}) - local container_top - if response.code == 200 then - container_top=response.body - else - response = dk.containers:top({id = container_id}) - if response.code == 200 then - container_top=response.body - end - end - - if type(container_top) == "table" then - container_top=response.body - stat_section = m:section(SimpleSection) - stat_section.container_id = container_id - stat_section.template = "dockerman/container_stats" - table_stats = {cpu={key=translate("CPU Useage"),value='-'},memory={key=translate("Memory Useage"),value='-'}} - stat_section = m:section(Table, table_stats, translate("Stats")) - stat_section:option(DummyValue, "key", translate("Stats")).width="33%" - stat_section:option(DummyValue, "value") - top_section= m:section(Table, container_top.Processes, translate("TOP")) - for i, v in ipairs(container_top.Titles) do - top_section:option(DummyValue, i, translate(v)) - end -end -m.submit = false -m.reset = false + local response = dk.containers:top({id = container_id, query = {ps_args="-aux"}}) + local container_top + + if response.code == 200 then + container_top=response.body + else + response = dk.containers:top({id = container_id}) + if response.code == 200 then + container_top=response.body + end + end + + if type(container_top) == "table" then + s = m:section(SimpleSection) + s.container_id = container_id + s.template = "dockerman/container_stats" + table_stats = { + cpu={ + key=translate("CPU Useage"), + value='-' + }, + memory={ + key=translate("Memory Useage"), + value='-' + } + } + + container_top = response.body + s = m:section(Table, table_stats, translate("Stats")) + s:option(DummyValue, "key", translate("Stats")).width="33%" + s:option(DummyValue, "value") + top_section = m:section(Table, container_top.Processes, translate("TOP")) + for i, v in ipairs(container_top.Titles) do + top_section:option(DummyValue, i, translate(v)) + end + end + + m.submit = false + m.reset = false end return m diff --git a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua index 2187de4662..cb1d2d943f 100644 --- a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua +++ b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/containers.lua @@ -3,193 +3,229 @@ LuCI - Lua Configuration Interface Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman> ]]-- -require "luci.util" local http = require "luci.http" -local uci = luci.model.uci.cursor() local docker = require "luci.model.docker" + +local m, s, o +local images, networks, containers, res + local dk = docker.new() +res = dk.images:list() +if res.code <300 then + images = res.body +else + return +end -local images, networks, containers -local res = dk.images:list() -if res.code <300 then images = res.body else return end res = dk.networks:list() -if res.code <300 then networks = res.body else return end -res = dk.containers:list({query = {all=true}}) -if res.code <300 then containers = res.body else return end +if res.code <300 then + networks = res.body +else + return +end + +res = dk.containers:list({ + query = { + all=true + } +}) +if res.code <300 then + containers = res.body +else + return +end local urlencode = luci.http.protocol and luci.http.protocol.urlencode or luci.util.urlencode function get_containers() - local data = {} - if type(containers) ~= "table" then return nil end - for i, v in ipairs(containers) do - local index = v.Created .. v.Id - data[index]={} - data[index]["_selected"] = 0 - data[index]["_id"] = v.Id:sub(1,12) - data[index]["name"] = v.Names[1]:sub(2) - data[index]["_name"] = '<a href='..luci.dispatcher.build_url("admin/docker/container/"..v.Id)..' class="dockerman_link" title="'..translate("Container detail")..'">'.. v.Names[1]:sub(2).."</a>" - data[index]["_status"] = v.Status - if v.Status:find("^Up") then - data[index]["_status"] = '<font color="green">'.. data[index]["_status"] .. "</font>" - else - data[index]["_status"] = '<font color="red">'.. data[index]["_status"] .. "</font>" - end - if (type(v.NetworkSettings) == "table" and type(v.NetworkSettings.Networks) == "table") then - for networkname, netconfig in pairs(v.NetworkSettings.Networks) do - data[index]["_network"] = (data[index]["_network"] ~= nil and (data[index]["_network"] .." | ") or "").. networkname .. (netconfig.IPAddress ~= "" and (": " .. netconfig.IPAddress) or "") - end - end - -- networkmode = v.HostConfig.NetworkMode ~= "default" and v.HostConfig.NetworkMode or "bridge" - -- data[index]["_network"] = v.NetworkSettings.Networks[networkmode].IPAddress or nil - -- local _, _, image = v.Image:find("^sha256:(.+)") - -- if image ~= nil then - -- image=image:sub(1,12) - -- end - if v.Ports and next(v.Ports) ~= nil then - data[index]["_ports"] = nil - for _,v2 in ipairs(v.Ports) do - data[index]["_ports"] = (data[index]["_ports"] and (data[index]["_ports"] .. ", ") or "") - .. ((v2.PublicPort and v2.Type and v2.Type == "tcp") and ('<a href="javascript:void(0);" onclick="window.open((window.location.origin.match(/^(.+):\\d+$/) && window.location.origin.match(/^(.+):\\d+$/)[1] || window.location.origin) + \':\' + '.. v2.PublicPort ..', \'_blank\');">') or "") - .. (v2.PublicPort and (v2.PublicPort .. ":") or "") .. (v2.PrivatePort and (v2.PrivatePort .."/") or "") .. (v2.Type and v2.Type or "") - .. ((v2.PublicPort and v2.Type and v2.Type == "tcp")and "</a>" or "") - end - end - for ii,iv in ipairs(images) do - if iv.Id == v.ImageID then - data[index]["_image"] = iv.RepoTags and iv.RepoTags[1] or (iv.RepoDigests[1]:gsub("(.-)@.+", "%1") .. ":<none>") - end - end - - data[index]["_image_id"] = v.ImageID:sub(8,20) - data[index]["_command"] = v.Command - end - return data + local data = {} + + if type(containers) ~= "table" then + return nil + end + + for i, v in ipairs(containers) do + local index = v.Created .. v.Id + + data[index]={} + data[index]["_selected"] = 0 + data[index]["_id"] = v.Id:sub(1,12) + data[index]["name"] = v.Names[1]:sub(2) + data[index]["_name"] = '<a href='..luci.dispatcher.build_url("admin/docker/container/"..v.Id)..' class="dockerman_link" title="'..translate("Container detail")..'">'.. v.Names[1]:sub(2).."</a>" + data[index]["_status"] = v.Status + + if v.Status:find("^Up") then + data[index]["_status"] = '<font color="green">'.. data[index]["_status"] .. "</font>" + else + data[index]["_status"] = '<font color="red">'.. data[index]["_status"] .. "</font>" + end + + if (type(v.NetworkSettings) == "table" and type(v.NetworkSettings.Networks) == "table") then + for networkname, netconfig in pairs(v.NetworkSettings.Networks) do + data[index]["_network"] = (data[index]["_network"] ~= nil and (data[index]["_network"] .." | ") or "").. networkname .. (netconfig.IPAddress ~= "" and (": " .. netconfig.IPAddress) or "") + end + end + + if v.Ports and next(v.Ports) ~= nil then + data[index]["_ports"] = nil + for _,v2 in ipairs(v.Ports) do + data[index]["_ports"] = (data[index]["_ports"] and (data[index]["_ports"] .. ", ") or "") + .. ((v2.PublicPort and v2.Type and v2.Type == "tcp") and ('<a href="javascript:void(0);" onclick="window.open((window.location.origin.match(/^(.+):\\d+$/) && window.location.origin.match(/^(.+):\\d+$/)[1] || window.location.origin) + \':\' + '.. v2.PublicPort ..', \'_blank\');">') or "") + .. (v2.PublicPort and (v2.PublicPort .. ":") or "") .. (v2.PrivatePort and (v2.PrivatePort .."/") or "") .. (v2.Type and v2.Type or "") + .. ((v2.PublicPort and v2.Type and v2.Type == "tcp")and "</a>" or "") + end + end + + for ii,iv in ipairs(images) do + if iv.Id == v.ImageID then + data[index]["_image"] = iv.RepoTags and iv.RepoTags[1] or (iv.RepoDigests[1]:gsub("(.-)@.+", "%1") .. ":<none>") + end + end + + data[index]["_image_id"] = v.ImageID:sub(8,20) + data[index]["_command"] = v.Command + end + + return data end -local c_lists = get_containers() --- list Containers --- m = Map("docker", translate("Docker")) +local container_list = get_containers() + m = SimpleForm("docker", translate("Docker")) m.submit=false m.reset=false -docker_status = m:section(SimpleSection) -docker_status.template = "dockerman/apply_widget" -docker_status.err=docker:read_status() -docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" "," ") -if docker_status.err then docker:clear_status() end - -c_table = m:section(Table, c_lists, translate("Containers")) -c_table.nodescr=true --- v.template = "cbi/tblsection" --- v.sortable = true -container_selecter = c_table:option(Flag, "_selected","") -container_selecter.disabled = 0 -container_selecter.enabled = 1 -container_selecter.default = 0 - -container_id = c_table:option(DummyValue, "_id", translate("ID")) -container_id.width="10%" -container_name = c_table:option(DummyValue, "_name", translate("Container Name")) -container_name.rawhtml = true -container_status = c_table:option(DummyValue, "_status", translate("Status")) -container_status.width="15%" -container_status.rawhtml=true -container_ip = c_table:option(DummyValue, "_network", translate("Network")) -container_ip.width="15%" -container_ports = c_table:option(DummyValue, "_ports", translate("Ports")) -container_ports.width="10%" -container_ports.rawhtml = true -container_image = c_table:option(DummyValue, "_image", translate("Image")) -container_image.width="10%" -container_command = c_table:option(DummyValue, "_command", translate("Command")) -container_command.width="20%" - -container_selecter.write=function(self, section, value) - c_lists[section]._selected = value +s = m:section(SimpleSection) +s.template = "dockerman/apply_widget" +s.err=docker:read_status() +s.err=s.err and s.err:gsub("\n","<br>"):gsub(" "," ") +if s.err then + docker:clear_status() +end + +s = m:section(Table, container_list, translate("Containers")) +s.nodescr=true + +o = s:option(Flag, "_selected","") +o.disabled = 0 +o.enabled = 1 +o.default = 0 +o.write=function(self, section, value) + container_list[section]._selected = value end +o = s:option(DummyValue, "_id", translate("ID")) +o.width="10%" + +o = s:option(DummyValue, "_name", translate("Container Name")) +o.rawhtml = true + +o = s:option(DummyValue, "_status", translate("Status")) +o.width="15%" +o.rawhtml=true + +o = s:option(DummyValue, "_network", translate("Network")) +o.width="15%" + +o = s:option(DummyValue, "_ports", translate("Ports")) +o.width="10%" +o.rawhtml = true + +o = s:option(DummyValue, "_image", translate("Image")) +o.width="10%" + +o = s:option(DummyValue, "_command", translate("Command")) +o.width="20%" + local start_stop_remove = function(m,cmd) - local c_selected = {} - -- 遍历table中sectionid - local c_table_sids = c_table:cfgsections() - for _, c_table_sid in ipairs(c_table_sids) do - -- 得到选中项的名字 - if c_lists[c_table_sid]._selected == 1 then - c_selected[#c_selected+1] = c_lists[c_table_sid].name --container_name:cfgvalue(c_table_sid) - end - end - if #c_selected >0 then - docker:clear_status() - local success = true - for _,cont in ipairs(c_selected) do - docker:append_status("Containers: " .. cmd .. " " .. cont .. "...") - local res = dk.containers[cmd](dk, {id = cont}) - if res and res.code >= 300 then - success = false - docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "\n") - else - docker:append_status("done\n") - end - end - if success then docker:clear_status() end - luci.http.redirect(luci.dispatcher.build_url("admin/docker/containers")) - end + local container_selected = {} + + for k in pairs(container_list) do + if container_list[k]._selected == 1 then + container_selected[#container_selected + 1] = container_list[k].name + end + end + + if #container_selected > 0 then + local success = true + + docker:clear_status() + for _, cont in ipairs(container_selected) do + docker:append_status("Containers: " .. cmd .. " " .. cont .. "...") + local res = dk.containers[cmd](dk, {id = cont}) + if res and res.code >= 300 then + success = false + docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "\n") + else + docker:append_status("done\n") + end + end + + if success then + docker:clear_status() + end + + luci.http.redirect(luci.dispatcher.build_url("admin/docker/containers")) + end end -action_section = m:section(Table,{{}}) -action_section.notitle=true -action_section.rowcolors=false -action_section.template="cbi/nullsection" - -btnnew=action_section:option(Button, "_new") -btnnew.inputtitle= translate("New") -btnnew.template = "dockerman/cbi/inlinebutton" -btnnew.inputstyle = "add" -btnnew.forcewrite = true -btnstart=action_section:option(Button, "_start") -btnstart.template = "dockerman/cbi/inlinebutton" -btnstart.inputtitle=translate("Start") -btnstart.inputstyle = "apply" -btnstart.forcewrite = true -btnrestart=action_section:option(Button, "_restart") -btnrestart.template = "dockerman/cbi/inlinebutton" -btnrestart.inputtitle=translate("Restart") -btnrestart.inputstyle = "reload" -btnrestart.forcewrite = true -btnstop=action_section:option(Button, "_stop") -btnstop.template = "dockerman/cbi/inlinebutton" -btnstop.inputtitle=translate("Stop") -btnstop.inputstyle = "reset" -btnstop.forcewrite = true -btnkill=action_section:option(Button, "_kill") -btnkill.template = "dockerman/cbi/inlinebutton" -btnkill.inputtitle=translate("Kill") -btnkill.inputstyle = "reset" -btnkill.forcewrite = true -btnremove=action_section:option(Button, "_remove") -btnremove.template = "dockerman/cbi/inlinebutton" -btnremove.inputtitle=translate("Remove") -btnremove.inputstyle = "remove" -btnremove.forcewrite = true -btnnew.write = function(self, section) - luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer")) +s = m:section(Table,{{}}) +s.notitle=true +s.rowcolors=false +s.template="cbi/nullsection" + +o = s:option(Button, "_new") +o.inputtitle= translate("New") +o.template = "dockerman/cbi/inlinebutton" +o.inputstyle = "add" +o.forcewrite = true +o.write = function(self, section) + luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer")) end -btnstart.write = function(self, section) - start_stop_remove(m,"start") + +o = s:option(Button, "_start") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle=translate("Start") +o.inputstyle = "apply" +o.forcewrite = true +o.write = function(self, section) + start_stop_remove(m,"start") end -btnrestart.write = function(self, section) - start_stop_remove(m,"restart") + +o = s:option(Button, "_restart") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle=translate("Restart") +o.inputstyle = "reload" +o.forcewrite = true +o.write = function(self, section) + start_stop_remove(m,"restart") end -btnremove.write = function(self, section) - start_stop_remove(m,"remove") + +o = s:option(Button, "_stop") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle=translate("Stop") +o.inputstyle = "reset" +o.forcewrite = true +o.write = function(self, section) + start_stop_remove(m,"stop") end -btnstop.write = function(self, section) - start_stop_remove(m,"stop") + +o = s:option(Button, "_kill") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle=translate("Kill") +o.inputstyle = "reset" +o.forcewrite = true +o.write = function(self, section) + start_stop_remove(m,"kill") end -btnkill.write = function(self, section) - start_stop_remove(m,"kill") + +o = s:option(Button, "_remove") +o.template = "dockerman/cbi/inlinebutton" +o.inputtitle=translate("Remove") +o.inputstyle = "remove" +o.forcewrite = true +o.write = function(self, section) + start_stop_remove(m,"remove") end return m diff --git a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua index 29d4a63573..01a7e3f237 100644 --- a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua +++ b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/images.lua @@ -3,221 +3,272 @@ LuCI - Lua Configuration Interface Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman> ]]-- -require "luci.util" -local uci = luci.model.uci.cursor() local docker = require "luci.model.docker" local dk = docker.new() -local containers, images -local res = dk.images:list() -if res.code <300 then images = res.body else return end -res = dk.containers:list({query = {all=true}}) -if res.code <300 then containers = res.body else return end +local containers, images, res +local m, s, o + +res = dk.images:list() +if res.code < 300 then + images = res.body +else + return +end + +res = dk.containers:list({ + query = { + all=true + } +}) +if res.code < 300 then + containers = res.body +else + return +end function get_images() - local data = {} - for i, v in ipairs(images) do - local index = v.Created .. v.Id - data[index]={} - data[index]["_selected"] = 0 - data[index]["id"] = v.Id:sub(8) - data[index]["_id"] = '<a href="javascript:new_tag(\''..v.Id:sub(8,20)..'\')" class="dockerman-link" title="'..translate("New tag")..'">' .. v.Id:sub(8,20) .. '</a>' - if v.RepoTags and next(v.RepoTags)~=nil then - for i, v1 in ipairs(v.RepoTags) do - data[index]["_tags"] =(data[index]["_tags"] and ( data[index]["_tags"] .. "<br>" )or "") .. ((v1:match("<none>") or (#v.RepoTags == 1)) and v1 or ('<a href="javascript:un_tag(\''..v1..'\')" class="dockerman_link" title="'..translate("Remove tag")..'" >' .. v1 .. '</a>')) - if not data[index]["tag"] then - data[index]["tag"] = v1--:match("<none>") and nil or v1 - end - end - else - data[index]["_tags"] = v.RepoDigests[1] and v.RepoDigests[1]:match("^(.-)@.+") - data[index]["_tags"] = (data[index]["_tags"] and data[index]["_tags"] or "<none>" ).. ":<none>" - end - data[index]["_tags"] = data[index]["_tags"]:gsub("<none>","<none>") - -- data[index]["_tags"] = '<a href="javascript:handle_tag(\''..data[index]["_id"]..'\')">' .. data[index]["_tags"] .. '</a>' - for ci,cv in ipairs(containers) do - if v.Id == cv.ImageID then - data[index]["_containers"] = (data[index]["_containers"] and (data[index]["_containers"] .. " | ") or "").. - '<a href='..luci.dispatcher.build_url("admin/docker/container/"..cv.Id)..' class="dockerman_link" title="'..translate("Container detail")..'">'.. cv.Names[1]:sub(2).."</a>" - end - end - data[index]["_size"] = string.format("%.2f", tostring(v.Size/1024/1024)).."MB" - data[index]["_created"] = os.date("%Y/%m/%d %H:%M:%S",v.Created) - end - return data + local data = {} + + for i, v in ipairs(images) do + local index = v.Created .. v.Id + + data[index]={} + data[index]["_selected"] = 0 + data[index]["id"] = v.Id:sub(8) + data[index]["_id"] = '<a href="javascript:new_tag(\''..v.Id:sub(8,20)..'\')" class="dockerman-link" title="'..translate("New tag")..'">' .. v.Id:sub(8,20) .. '</a>' + + if v.RepoTags and next(v.RepoTags)~=nil then + for i, v1 in ipairs(v.RepoTags) do + data[index]["_tags"] =(data[index]["_tags"] and ( data[index]["_tags"] .. "<br>" )or "") .. ((v1:match("<none>") or (#v.RepoTags == 1)) and v1 or ('<a href="javascript:un_tag(\''..v1..'\')" class="dockerman_link" title="'..translate("Remove tag")..'" >' .. v1 .. '</a>')) + + if not data[index]["tag"] then + data[index]["tag"] = v1 + end + end + else + data[index]["_tags"] = v.RepoDigests[1] and v.RepoDigests[1]:match("^(.-)@.+") + data[index]["_tags"] = (data[index]["_tags"] and data[index]["_tags"] or "<none>" ).. ":<none>" + end + + data[index]["_tags"] = data[index]["_tags"]:gsub("<none>","<none>") + for ci,cv in ipairs(containers) do + if v.Id == cv.ImageID then + data[index]["_containers"] = (data[index]["_containers"] and (data[index]["_containers"] .. " | ") or "").. + '<a href='..luci.dispatcher.build_url("admin/docker/container/"..cv.Id)..' class="dockerman_link" title="'..translate("Container detail")..'">'.. cv.Names[1]:sub(2).."</a>" + end + end + + data[index]["_size"] = string.format("%.2f", tostring(v.Size/1024/1024)).."MB" + data[index]["_created"] = os.date("%Y/%m/%d %H:%M:%S",v.Created) + end + + return data end local image_list = get_images() --- m = Map("docker", translate("Docker")) m = SimpleForm("docker", translate("Docker")) m.submit=false m.reset=false -local pull_value={_image_tag_name="", _registry="index.docker.io"} -local pull_section = m:section(SimpleSection, translate("Pull Image")) -pull_section.template="cbi/nullsection" -local tag_name = pull_section:option(Value, "_image_tag_name") -tag_name.template = "dockerman/cbi/inlinevalue" -tag_name.placeholder="lisaac/luci:latest" -local action_pull = pull_section:option(Button, "_pull") -action_pull.inputtitle= translate("Pull") -action_pull.template = "dockerman/cbi/inlinebutton" -action_pull.inputstyle = "add" -tag_name.write = function(self, section, value) - local hastag = value:find(":") - if not hastag then - value = value .. ":latest" - end - pull_value["_image_tag_name"] = value +local pull_value={ + _image_tag_name="", + _registry="index.docker.io" +} + +s = m:section(SimpleSection, translate("Pull Image")) +s.template="cbi/nullsection" + +o = s:option(Value, "_image_tag_name") +o.template = "dockerman/cbi/inlinevalue" +o.placeholder="lisaac/luci:latest" +o.write = function(self, section, value) + local hastag = value:find(":") + + if not hastag then + value = value .. ":latest" + end + pull_value["_image_tag_name"] = value end -action_pull.write = function(self, section) - local tag = pull_value["_image_tag_name"] - local json_stringify = luci.jsonc and luci.jsonc.stringify - if tag and tag ~= "" then - docker:write_status("Images: " .. "pulling" .. " " .. tag .. "...\n") - -- local x_auth = nixio.bin.b64encode(json_stringify({serveraddress= server})) , header={["X-Registry-Auth"] = x_auth} - local res = dk.images:create({query = {fromImage=tag}}, docker.pull_image_show_status_cb) - -- {"errorDetail": {"message": "failed to register layer: ApplyLayer exit status 1 stdout: stderr: write \/docker: no space left on device" }, "error": "failed to register layer: ApplyLayer exit status 1 stdout: stderr: write \/docker: no space left on device" } - if res and res.code == 200 and (res.body[#res.body] and not res.body[#res.body].error and res.body[#res.body].status and (res.body[#res.body].status == "Status: Downloaded newer image for ".. tag)) then - docker:clear_status() - else - docker:append_status("code:" .. res.code.." ".. (res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message)).. "\n") - end - else - docker:append_status("code: 400 please input the name of image name!") - end - luci.http.redirect(luci.dispatcher.build_url("admin/docker/images")) + +o = s:option(Button, "_pull") +o.inputtitle= translate("Pull") +o.template = "dockerman/cbi/inlinebutton" +o.inputstyle = "add" +o.write = function(self, section) + local tag = pull_value["_image_tag_name"] + local json_stringify = luci.jsonc and luci.jsonc.stringify + + if tag and tag ~= "" then + docker:write_status("Images: " .. "pulling" .. " " .. tag .. "...\n") + local res = dk.images:create({query = {fromImage=tag}}, docker.pull_image_show_status_cb) + + if res and res.code == 200 and (res.body[#res.body] and not res.body[#res.body].error and res.body[#res.body].status and (res.body[#res.body].status == "Status: Downloaded newer image for ".. tag)) then + docker:clear_status() + else + docker:append_status("code:" .. res.code.." ".. (res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message)).. "\n") + end + else + docker:append_status("code: 400 please input the name of image name!") + end + + luci.http.redirect(luci.dispatcher.build_url("admin/docker/images")) end -local import_section = m:section(SimpleSection, translate("Import Images")) -local im = import_section:option(DummyValue, "_image_import") -im.template = "dockerman/images_import" - -local image_table = m:section(Table, image_list, translate("Images")) - -local image_selecter = image_table:option(Flag, "_selected","") -image_selecter.disabled = 0 -image_selecter.enabled = 1 -image_selecter.default = 0 - -local image_id = image_table:option(DummyValue, "_id", translate("ID")) -image_id.rawhtml = true -image_table:option(DummyValue, "_tags", translate("RepoTags")).rawhtml = true -image_table:option(DummyValue, "_containers", translate("Containers")).rawhtml = true -image_table:option(DummyValue, "_size", translate("Size")) -image_table:option(DummyValue, "_created", translate("Created")) -image_selecter.write = function(self, section, value) - image_list[section]._selected = value +s = m:section(SimpleSection, translate("Import Images")) + +o = s:option(DummyValue, "_image_import") +o.template = "dockerman/images_import" + +s = m:section(Table, image_list, translate("Images")) + +o = s:option(Flag, "_selected","") +o.disabled = 0 +o.enabled = 1 +o.default = 0 +o.write = function(self, section, value) + image_list[section]._selected = value end +o = s:option(DummyValue, "_tags", translate("RepoTags")) +o.rawhtml = true + +o = s:option(DummyValue, "_containers", translate("Containers")) +o.rawhtml = true + +o = s:option(DummyValue, "_size", translate("Size")) + +o = s:option(DummyValue, "_created", translate("Created")) + +o = s:option(DummyValue, "_id", translate("ID")) +o.rawhtml = true + local remove_action = function(force) - local image_selected = {} - -- 遍历table中sectionid - local image_table_sids = image_table:cfgsections() - for _, image_table_sid in ipairs(image_table_sids) do - -- 得到选中项的名字 - if image_list[image_table_sid]._selected == 1 then - image_selected[#image_selected+1] = (image_list[image_table_sid]["_tags"]:match("<br>") or image_list[image_table_sid]["_tags"]:match("<none>")) and image_list[image_table_sid].id or image_list[image_table_sid].tag - end - end - if next(image_selected) ~= nil then - local success = true - docker:clear_status() - for _,img in ipairs(image_selected) do - docker:append_status("Images: " .. "remove" .. " " .. img .. "...") - local query - if force then query = {force = true} end - local msg = dk.images:remove({id = img, query = query}) - if msg.code ~= 200 then - docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n") - success = false - else - docker:append_status("done\n") - end - end - if success then docker:clear_status() end - luci.http.redirect(luci.dispatcher.build_url("admin/docker/images")) - end + local image_selected = {} + + for k in pairs(image_list) do + if image_list[k]._selected == 1 then + image_selected[#image_selected+1] = (image_list[k]["_tags"]:match("<br>") or image_list[k]["_tags"]:match("<none>")) and image_list[k].id or image_list[k].tag + end + end + + if next(image_selected) ~= nil then + local success = true + + docker:clear_status() + for _, img in ipairs(image_selected) do + local query + docker:append_status("Images: " .. "remove" .. " " .. img .. "...") + + if force then + query = {force = true} + end + + local msg = dk.images:remove({ + id = img, + query = query + }) + if msg.code ~= 200 then + docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n") + success = false + else + docker:append_status("done\n") + end + end + + if success then + docker:clear_status() + end + + luci.http.redirect(luci.dispatcher.build_url("admin/docker/images")) + end end -local docker_status = m:section(SimpleSection) -docker_status.template = "dockerman/apply_widget" -docker_status.err = docker:read_status() -docker_status.err = docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" "," ") -if docker_status.err then docker:clear_status() end - -local action = m:section(Table,{{}}) -action.notitle=true -action.rowcolors=false -action.template="cbi/nullsection" - -local btnremove = action:option(Button, "remove") -btnremove.inputtitle= translate("Remove") -btnremove.template = "dockerman/cbi/inlinebutton" -btnremove.inputstyle = "remove" -btnremove.forcewrite = true -btnremove.write = function(self, section) - remove_action() +s = m:section(SimpleSection) +s.template = "dockerman/apply_widget" +s.err = docker:read_status() +s.err = s.err and s.err:gsub("\n","<br>"):gsub(" "," ") +if s.err then + docker:clear_status() end -local btnforceremove = action:option(Button, "forceremove") -btnforceremove.inputtitle= translate("Force Remove") -btnforceremove.template = "dockerman/cbi/inlinebutton" -btnforceremove.inputstyle = "remove" -btnforceremove.forcewrite = true -btnforceremove.write = function(self, section) - remove_action(true) +s = m:section(Table,{{}}) +s.notitle=true +s.rowcolors=false +s.template="cbi/nullsection" + +o = s:option(Button, "remove") +o.inputtitle= translate("Remove") +o.template = "dockerman/cbi/inlinebutton" +o.inputstyle = "remove" +o.forcewrite = true +o.write = function(self, section) + remove_action() +end + +o = s:option(Button, "forceremove") +o.inputtitle= translate("Force Remove") +o.template = "dockerman/cbi/inlinebutton" +o.inputstyle = "remove" +o.forcewrite = true +o.write = function(self, section) + remove_action(true) end -local btnsave = action:option(Button, "save") -btnsave.inputtitle= translate("Save") -btnsave.template = "dockerman/cbi/inlinebutton" -btnsave.inputstyle = "edit" -btnsave.forcewrite = true -btnsave.write = function (self, section) - local image_selected = {} - local image_table_sids = image_table:cfgsections() - for _, image_table_sid in ipairs(image_table_sids) do - if image_list[image_table_sid]._selected == 1 then - image_selected[#image_selected+1] = image_list[image_table_sid].id --image_id:cfgvalue(image_table_sid) - end - end - if next(image_selected) ~= nil then - local names - for _,img in ipairs(image_selected) do - names = names and (names .. "&names=".. img) or img - end - local first - local cb = function(res, chunk) - if res.code == 200 then - if not first then - first = true - luci.http.header('Content-Disposition', 'inline; filename="images.tar"') - luci.http.header('Content-Type', 'application\/x-tar') - end - luci.ltn12.pump.all(chunk, luci.http.write) - else - if not first then - first = true - luci.http.prepare_content("text/plain") - end - luci.ltn12.pump.all(chunk, luci.http.write) - end - end - docker:write_status("Images: " .. "save" .. " " .. table.concat(image_selected, "\n") .. "...") - local msg = dk.images:get({query = {names = names}}, cb) - if msg.code ~= 200 then - docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n") - success = false - else - docker:clear_status() - end - end +o = s:option(Button, "save") +o.inputtitle= translate("Save") +o.template = "dockerman/cbi/inlinebutton" +o.inputstyle = "edit" +o.forcewrite = true +o.write = function (self, section) + local image_selected = {} + + for k in pairs(image_list) do + if image_list[k]._selected == 1 then + image_selected[#image_selected + 1] = image_list[k].id + end + end + + if next(image_selected) ~= nil then + local names, first + + for _, img in ipairs(image_selected) do + names = names and (names .. "&names=".. img) or img + end + + local cb = function(res, chunk) + if res.code == 200 then + if not first then + first = true + luci.http.header('Content-Disposition', 'inline; filename="images.tar"') + luci.http.header('Content-Type', 'application\/x-tar') + end + luci.ltn12.pump.all(chunk, luci.http.write) + else + if not first then + first = true + luci.http.prepare_content("text/plain") + end + luci.ltn12.pump.all(chunk, luci.http.write) + end + end + + docker:write_status("Images: " .. "save" .. " " .. table.concat(image_selected, "\n") .. "...") + local msg = dk.images:get({query = {names = names}}, cb) + + if msg.code ~= 200 then + docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n") + success = false + else + docker:clear_status() + end + end end -local btnload = action:option(Button, "load") -btnload.inputtitle= translate("Load") -btnload.template = "dockerman/images_load" -btnload.inputstyle = "add" +o = s:option(Button, "load") +o.inputtitle= translate("Load") +o.template = "dockerman/images_load" +o.inputstyle = "add" + return m diff --git a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua index e8e392f6ab..4cea32915d 100644 --- a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua +++ b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/networks.lua @@ -3,128 +3,150 @@ LuCI - Lua Configuration Interface Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman> ]]-- -require "luci.util" -local uci = luci.model.uci.cursor() local docker = require "luci.model.docker" -local dk = docker.new() -local networks -local res = dk.networks:list() -if res.code < 300 then networks = res.body else return end + +local m, s, o +local networks, dk, res + +dk = docker.new() +res = dk.networks:list() +if res.code < 300 then + networks = res.body +else + return +end local get_networks = function () - local data = {} - - if type(networks) ~= "table" then return nil end - for i, v in ipairs(networks) do - local index = v.Created .. v.Id - data[index]={} - data[index]["_selected"] = 0 - data[index]["_id"] = v.Id:sub(1,12) - data[index]["_name"] = v.Name - data[index]["_driver"] = v.Driver - if v.Driver == "bridge" then - data[index]["_interface"] = v.Options["com.docker.network.bridge.name"] - elseif v.Driver == "macvlan" then - data[index]["_interface"] = v.Options.parent - end - data[index]["_subnet"] = v.IPAM and v.IPAM.Config[1] and v.IPAM.Config[1].Subnet or nil - data[index]["_gateway"] = v.IPAM and v.IPAM.Config[1] and v.IPAM.Config[1].Gateway or nil - end - return data + local data = {} + + if type(networks) ~= "table" then + return nil + end + + for i, v in ipairs(networks) do + local index = v.Created .. v.Id + + data[index]={} + data[index]["_selected"] = 0 + data[index]["_id"] = v.Id:sub(1,12) + data[index]["_name"] = v.Name + data[index]["_driver"] = v.Driver + + if v.Driver == "bridge" then + data[index]["_interface"] = v.Options["com.docker.network.bridge.name"] + elseif v.Driver == "macvlan" then + data[index]["_interface"] = v.Options.parent + end + + data[index]["_subnet"] = v.IPAM and v.IPAM.Config[1] and v.IPAM.Config[1].Subnet or nil + data[index]["_gateway"] = v.IPAM and v.IPAM.Config[1] and v.IPAM.Config[1].Gateway or nil + end + + return data end local network_list = get_networks() --- m = Map("docker", translate("Docker")) + m = SimpleForm("docker", translate("Docker")) m.submit=false m.reset=false -network_table = m:section(Table, network_list, translate("Networks")) -network_table.nodescr=true - -network_selecter = network_table:option(Flag, "_selected","") -network_selecter.template = "dockerman/cbi/xfvalue" -network_id = network_table:option(DummyValue, "_id", translate("ID")) -network_selecter.disabled = 0 -network_selecter.enabled = 1 -network_selecter.default = 0 -network_selecter.render = function(self, section, scope) - self.disable = 0 - if network_list[section]["_name"] == "bridge" or network_list[section]["_name"] == "none" or network_list[section]["_name"] == "host" then - self.disable = 1 - end - Flag.render(self, section, scope) +s = m:section(Table, network_list, translate("Networks")) +s.nodescr=true + +o = s:option(Flag, "_selected","") +o.template = "dockerman/cbi/xfvalue" +o.disabled = 0 +o.enabled = 1 +o.default = 0 +o.render = function(self, section, scope) + self.disable = 0 + if network_list[section]["_name"] == "bridge" or network_list[section]["_name"] == "none" or network_list[section]["_name"] == "host" then + self.disable = 1 + end + Flag.render(self, section, scope) +end +o.write = function(self, section, value) + network_list[section]._selected = value end -network_name = network_table:option(DummyValue, "_name", translate("Network Name")) -network_driver = network_table:option(DummyValue, "_driver", translate("Driver")) -network_interface = network_table:option(DummyValue, "_interface", translate("Parent Interface")) -network_subnet = network_table:option(DummyValue, "_subnet", translate("Subnet")) -network_gateway = network_table:option(DummyValue, "_gateway", translate("Gateway")) +o = s:option(DummyValue, "_id", translate("ID")) -network_selecter.write = function(self, section, value) - network_list[section]._selected = value +o = s:option(DummyValue, "_name", translate("Network Name")) + +o = s:option(DummyValue, "_driver", translate("Driver")) + +o = s:option(DummyValue, "_interface", translate("Parent Interface")) + +o = s:option(DummyValue, "_subnet", translate("Subnet")) + +o = s:option(DummyValue, "_gateway", translate("Gateway")) + +s = m:section(SimpleSection) +s.template = "dockerman/apply_widget" +s.err = docker:read_status() +s.err = s.err and s.err:gsub("\n","<br>"):gsub(" "," ") +if s.err then + docker:clear_status() end -docker_status = m:section(SimpleSection) -docker_status.template = "dockerman/apply_widget" -docker_status.err=docker:read_status() -docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" "," ") -if docker_status.err then docker:clear_status() end - -action = m:section(Table,{{}}) -action.notitle=true -action.rowcolors=false -action.template="cbi/nullsection" -btnnew=action:option(Button, "_new") -btnnew.inputtitle= translate("New") -btnnew.template = "dockerman/cbi/inlinebutton" -btnnew.notitle=true -btnnew.inputstyle = "add" -btnnew.forcewrite = true -btnnew.write = function(self, section) - luci.http.redirect(luci.dispatcher.build_url("admin/docker/newnetwork")) +s = m:section(Table,{{}}) +s.notitle=true +s.rowcolors=false +s.template="cbi/nullsection" + +o = s:option(Button, "_new") +o.inputtitle= translate("New") +o.template = "dockerman/cbi/inlinebutton" +o.notitle=true +o.inputstyle = "add" +o.forcewrite = true +o.write = function(self, section) + luci.http.redirect(luci.dispatcher.build_url("admin/docker/newnetwork")) end -btnremove = action:option(Button, "_remove") -btnremove.inputtitle= translate("Remove") -btnremove.template = "dockerman/cbi/inlinebutton" -btnremove.inputstyle = "remove" -btnremove.forcewrite = true -btnremove.write = function(self, section) - local network_selected = {} - local network_name_selected = {} - local network_driver_selected = {} - -- 遍历table中sectionid - local network_table_sids = network_table:cfgsections() - for _, network_table_sid in ipairs(network_table_sids) do - -- 得到选中项的名字 - if network_list[network_table_sid]._selected == 1 then - network_selected[#network_selected+1] = network_list[network_table_sid]._id --network_name:cfgvalue(network_table_sid) - network_name_selected[#network_name_selected+1] = network_list[network_table_sid]._name - network_driver_selected[#network_driver_selected+1] = network_list[network_table_sid]._driver - end - end - if next(network_selected) ~= nil then - local success = true - docker:clear_status() - for ii, net in ipairs(network_selected) do - docker:append_status("Networks: " .. "remove" .. " " .. net .. "...") - local res = dk.networks["remove"](dk, {id = net}) - if res and res.code >= 300 then - docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "\n") - success = false - else - docker:append_status("done\n") - if network_driver_selected[ii] == "macvlan" then - docker.remove_macvlan_interface(network_name_selected[ii]) - end - end - end - if success then - docker:clear_status() - end - luci.http.redirect(luci.dispatcher.build_url("admin/docker/networks")) - end + +o = s:option(Button, "_remove") +o.inputtitle= translate("Remove") +o.template = "dockerman/cbi/inlinebutton" +o.inputstyle = "remove" +o.forcewrite = true +o.write = function(self, section) + local network_selected = {} + local network_name_selected = {} + local network_driver_selected = {} + + for k in pairs(network_list) do + if network_list[k]._selected == 1 then + network_selected[#network_selected + 1] = network_list[k]._id + network_name_selected[#network_name_selected + 1] = network_list[k]._name + network_driver_selected[#network_driver_selected + 1] = network_list[k]._driver + end + end + + if next(network_selected) ~= nil then + local success = true + docker:clear_status() + + for ii, net in ipairs(network_selected) do + docker:append_status("Networks: " .. "remove" .. " " .. net .. "...") + local res = dk.networks["remove"](dk, {id = net}) + + if res and res.code >= 300 then + docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "\n") + success = false + else + docker:append_status("done\n") + if network_driver_selected[ii] == "macvlan" then + docker.remove_macvlan_interface(network_name_selected[ii]) + end + end + end + + if success then + docker:clear_status() + end + luci.http.redirect(luci.dispatcher.build_url("admin/docker/networks")) + end end return m diff --git a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua index 324fc6dd70..0ee344370e 100644 --- a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua +++ b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newcontainer.lua @@ -3,298 +3,502 @@ LuCI - Lua Configuration Interface Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman> ]]-- -require "luci.util" -local uci = luci.model.uci.cursor() local docker = require "luci.model.docker" + +local m, s, o + local dk = docker.new() + local cmd_line = table.concat(arg, '/') local create_body = {} local images = dk.images:list().body local networks = dk.networks:list().body -local containers = dk.containers:list({query = {all=true}}).body +local containers = dk.containers:list({ + query = { + all=true + } +}).body local is_quot_complete = function(str) - require "math" - if not str then return true end - local num = 0, w - for w in str:gmatch("\"") do - num = num + 1 - end - if math.fmod(num, 2) ~= 0 then return false end - num = 0 - for w in str:gmatch("\'") do - num = num + 1 - end - if math.fmod(num, 2) ~= 0 then return false end - return true + local num = 0, w + require "math" + + if not str then + return true + end + + local num = 0, w + for w in str:gmatch("\"") do + num = num + 1 + end + + if math.fmod(num, 2) ~= 0 then + return false + end + + num = 0 + for w in str:gmatch("\'") do + num = num + 1 + end + + if math.fmod(num, 2) ~= 0 then + return false + end + + return true +end + +function contains(list, x) + for _, v in pairs(list) do + if v == x then + return true + end + end + return false end local resolve_cli = function(cmd_line) - local config = {advance = 1} - local key_no_val = '|t|d|i|tty|rm|read_only|interactive|init|help|detach|privileged|P|publish_all|' - local key_with_val = '|sysctl|add_host|a|attach|blkio_weight_device|cap_add|cap_drop|device|device_cgroup_rule|device_read_bps|device_read_iops|device_write_bps|device_write_iops|dns|dns_option|dns_search|e|env|env_file|expose|group_add|l|label|label_file|link|link_local_ip|log_driver|log_opt|network_alias|p|publish|security_opt|storage_opt|tmpfs|v|volume|volumes_from|blkio_weight|cgroup_parent|cidfile|cpu_period|cpu_quota|cpu_rt_period|cpu_rt_runtime|c|cpu_shares|cpus|cpuset_cpus|cpuset_mems|detach_keys|disable_content_trust|domainname|entrypoint|gpus|health_cmd|health_interval|health_retries|health_start_period|health_timeout|h|hostname|ip|ip6|ipc|isolation|kernel_memory|log_driver|mac_address|m|memory|memory_reservation|memory_swap|memory_swappiness|mount|name|network|no_healthcheck|oom_kill_disable|oom_score_adj|pid|pids_limit|restart|runtime|shm_size|sig_proxy|stop_signal|stop_timeout|ulimit|u|user|userns|uts|volume_driver|w|workdir|' - local key_abb = {net='network',a='attach',c='cpu-shares',d='detach',e='env',h='hostname',i='interactive',l='label',m='memory',p='publish',P='publish_all',t='tty',u='user',v='volume',w='workdir'} - local key_with_list = '|sysctl|add_host|a|attach|blkio_weight_device|cap_add|cap_drop|device|device_cgroup_rule|device_read_bps|device_read_iops|device_write_bps|device_write_iops|dns|dns_option|dns_search|e|env|env_file|expose|group_add|l|label|label_file|link|link_local_ip|log_driver|log_opt|network_alias|p|publish|security_opt|storage_opt|tmpfs|v|volume|volumes_from|' - local key = nil - local _key = nil - local val = nil - local is_cmd = false - - cmd_line = cmd_line:match("^DOCKERCLI%s+(.+)") - for w in cmd_line:gmatch("[^%s]+") do - if w =='\\' then - elseif not key and not _key and not is_cmd then - --key=val - key, val = w:match("^%-%-([%lP%-]-)=(.+)") - if not key then - --key val - key = w:match("^%-%-([%lP%-]+)") - if not key then - -- -v val - key = w:match("^%-([%lP%-]+)") - if key then - -- for -dit - if key:match("i") or key:match("t") or key:match("d") then - if key:match("i") then - config[key_abb["i"]] = true - key:gsub("i", "") - end - if key:match("t") then - config[key_abb["t"]] = true - key:gsub("t", "") - end - if key:match("d") then - config[key_abb["d"]] = true - key:gsub("d", "") - end - if key:match("P") then - config[key_abb["P"]] = true - key:gsub("P", "") - end - if key == "" then key = nil end - end - end - end - end - if key then - key = key:gsub("-","_") - key = key_abb[key] or key - if key_no_val:match("|"..key.."|") then - config[key] = true - val = nil - key = nil - elseif key_with_val:match("|"..key.."|") then - -- if key == "cap_add" then config.privileged = true end - else - key = nil - val = nil - end - else - config.image = w - key = nil - val = nil - is_cmd = true - end - elseif (key or _key) and not is_cmd then - if key == "mount" then - -- we need resolve mount options here - -- type=bind,source=/source,target=/app - local _type = w:match("^type=([^,]+),") or "bind" - local source = (_type ~= "tmpfs") and (w:match("source=([^,]+),") or w:match("src=([^,]+),")) or "" - local target = w:match(",target=([^,]+)") or w:match(",dst=([^,]+)") or w:match(",destination=([^,]+)") or "" - local ro = w:match(",readonly") and "ro" or nil - if source and target then - if _type ~= "tmpfs" then - -- bind or volume - local bind_propagation = (_type == "bind") and w:match(",bind%-propagation=([^,]+)") or nil - val = source..":"..target .. ((ro or bind_propagation) and (":" .. (ro and ro or "") .. (((ro and bind_propagation) and "," or "") .. (bind_propagation and bind_propagation or ""))or "")) - else - -- tmpfs - local tmpfs_mode = w:match(",tmpfs%-mode=([^,]+)") or nil - local tmpfs_size = w:match(",tmpfs%-size=([^,]+)") or nil - key = "tmpfs" - val = target .. ((tmpfs_mode or tmpfs_size) and (":" .. (tmpfs_mode and ("mode=" .. tmpfs_mode) or "") .. ((tmpfs_mode and tmpfs_size) and "," or "") .. (tmpfs_size and ("size=".. tmpfs_size) or "")) or "") - if not config[key] then config[key] = {} end - table.insert( config[key], val ) - key = nil - val = nil - end - end - else - val = w - end - elseif is_cmd then - config["command"] = (config["command"] and (config["command"] .. " " )or "") .. w - end - if (key or _key) and val then - key = _key or key - if key_with_list:match("|"..key.."|") then - if not config[key] then config[key] = {} end - if _key then - config[key][#config[key]] = config[key][#config[key]] .. " " .. w - else - table.insert( config[key], val ) - end - if is_quot_complete(config[key][#config[key]]) then - -- clear quotation marks - config[key][#config[key]] = config[key][#config[key]]:gsub("[\"\']", "") - _key = nil - else - _key = key - end - else - config[key] = (config[key] and (config[key] .. " ") or "") .. val - if is_quot_complete(config[key]) then - -- clear quotation marks - config[key] = config[key]:gsub("[\"\']", "") - _key = nil - else - _key = key - end - end - key = nil - val = nil - end - end - return config + local config = { + advance = 1 + } + + local key_no_val = { + 't', + 'd', + 'i', + 'tty', + 'rm', + 'read_only', + 'interactive', + 'init', + 'help', + 'detach', + 'privileged', + 'P', + 'publish_all', + } + + local key_with_val = { + 'sysctl', + 'add_host', + 'a', + 'attach', + 'blkio_weight_device', + 'cap_add', + 'cap_drop', + 'device', + 'device_cgroup_rule', + 'device_read_bps', + 'device_read_iops', + 'device_write_bps', + 'device_write_iops', + 'dns', + 'dns_option', + 'dns_search', + 'e', + 'env', + 'env_file', + 'expose', + 'group_add', + 'l', + 'label', + 'label_file', + 'link', + 'link_local_ip', + 'log_driver', + 'log_opt', + 'network_alias', + 'p', + 'publish', + 'security_opt', + 'storage_opt', + 'tmpfs', + 'v', + 'volume', + 'volumes_from', + 'blkio_weight', + 'cgroup_parent', + 'cidfile', + 'cpu_period', + 'cpu_quota', + 'cpu_rt_period', + 'cpu_rt_runtime', + 'c', + 'cpu_shares', + 'cpus', + 'cpuset_cpus', + 'cpuset_mems', + 'detach_keys', + 'disable_content_trust', + 'domainname', + 'entrypoint', + 'gpus', + 'health_cmd', + 'health_interval', + 'health_retries', + 'health_start_period', + 'health_timeout', + 'h', + 'hostname', + 'ip', + 'ip6', + 'ipc', + 'isolation', + 'kernel_memory', + 'log_driver', + 'mac_address', + 'm', + 'memory', + 'memory_reservation', + 'memory_swap', + 'memory_swappiness', + 'mount', + 'name', + 'network', + 'no_healthcheck', + 'oom_kill_disable', + 'oom_score_adj', + 'pid', + 'pids_limit', + 'restart', + 'runtime', + 'shm_size', + 'sig_proxy', + 'stop_signal', + 'stop_timeout', + 'ulimit', + 'u', + 'user', + 'userns', + 'uts', + 'volume_driver', + 'w', + 'workdir' + } + + local key_abb = { + net='network', + a='attach', + c='cpu-shares', + d='detach', + e='env', + h='hostname', + i='interactive', + l='label', + m='memory', + p='publish', + P='publish_all', + t='tty', + u='user', + v='volume', + w='workdir' + } + + local key_with_list = { + 'sysctl', + 'add_host', + 'a', + 'attach', + 'blkio_weight_device', + 'cap_add', + 'cap_drop', + 'device', + 'device_cgroup_rule', + 'device_read_bps', + 'device_read_iops', + 'device_write_bps', + 'device_write_iops', + 'dns', + 'dns_optiondns_search', + 'e', + 'env', + 'env_file', + 'expose', + 'group_add', + 'l', + 'label', + 'label_file', + 'link', + 'link_local_ip', + 'log_driver', + 'log_opt', + 'network_alias', + 'p', + 'publish', + 'security_opt', + 'storage_opt', + 'tmpfs', + 'v', + 'volume', + 'volumes_from', + } + + local key = nil + local _key = nil + local val = nil + local is_cmd = false + + cmd_line = cmd_line:match("^DOCKERCLI%s+(.+)") + for w in cmd_line:gmatch("[^%s]+") do + if w =='\\' then + elseif not key and not _key and not is_cmd then + --key=val + key, val = w:match("^%-%-([%lP%-]-)=(.+)") + if not key then + --key val + key = w:match("^%-%-([%lP%-]+)") + if not key then + -- -v val + key = w:match("^%-([%lP%-]+)") + if key then + -- for -dit + if key:match("i") or key:match("t") or key:match("d") then + if key:match("i") then + config[key_abb["i"]] = true + key:gsub("i", "") + end + if key:match("t") then + config[key_abb["t"]] = true + key:gsub("t", "") + end + if key:match("d") then + config[key_abb["d"]] = true + key:gsub("d", "") + end + if key:match("P") then + config[key_abb["P"]] = true + key:gsub("P", "") + end + if key == "" then + key = nil + end + end + end + end + end + if key then + key = key:gsub("-","_") + key = key_abb[key] or key + if contains(key_no_val, key) then + config[key] = true + val = nil + key = nil + elseif contains(key_with_val, key) then + -- if key == "cap_add" then config.privileged = true end + else + key = nil + val = nil + end + else + config.image = w + key = nil + val = nil + is_cmd = true + end + elseif (key or _key) and not is_cmd then + if key == "mount" then + -- we need resolve mount options here + -- type=bind,source=/source,target=/app + local _type = w:match("^type=([^,]+),") or "bind" + local source = (_type ~= "tmpfs") and (w:match("source=([^,]+),") or w:match("src=([^,]+),")) or "" + local target = w:match(",target=([^,]+)") or w:match(",dst=([^,]+)") or w:match(",destination=([^,]+)") or "" + local ro = w:match(",readonly") and "ro" or nil + + if source and target then + if _type ~= "tmpfs" then + local bind_propagation = (_type == "bind") and w:match(",bind%-propagation=([^,]+)") or nil + val = source..":"..target .. ((ro or bind_propagation) and (":" .. (ro and ro or "") .. (((ro and bind_propagation) and "," or "") .. (bind_propagation and bind_propagation or ""))or "")) + else + local tmpfs_mode = w:match(",tmpfs%-mode=([^,]+)") or nil + local tmpfs_size = w:match(",tmpfs%-size=([^,]+)") or nil + key = "tmpfs" + val = target .. ((tmpfs_mode or tmpfs_size) and (":" .. (tmpfs_mode and ("mode=" .. tmpfs_mode) or "") .. ((tmpfs_mode and tmpfs_size) and "," or "") .. (tmpfs_size and ("size=".. tmpfs_size) or "")) or "") + if not config[key] then + config[key] = {} + end + table.insert( config[key], val ) + key = nil + val = nil + end + end + else + val = w + end + elseif is_cmd then + config["command"] = (config["command"] and (config["command"] .. " " )or "") .. w + end + if (key or _key) and val then + key = _key or key + if contains(key_with_list, key) then + if not config[key] then + config[key] = {} + end + if _key then + config[key][#config[key]] = config[key][#config[key]] .. " " .. w + else + table.insert( config[key], val ) + end + if is_quot_complete(config[key][#config[key]]) then + config[key][#config[key]] = config[key][#config[key]]:gsub("[\"\']", "") + _key = nil + else + _key = key + end + else + config[key] = (config[key] and (config[key] .. " ") or "") .. val + if is_quot_complete(config[key]) then + config[key] = config[key]:gsub("[\"\']", "") + _key = nil + else + _key = key + end + end + key = nil + val = nil + end + end + + return config end --- reslvo default config + local default_config = {} + if cmd_line and cmd_line:match("^DOCKERCLI.+") then - default_config = resolve_cli(cmd_line) + default_config = resolve_cli(cmd_line) elseif cmd_line and cmd_line:match("^duplicate/[^/]+$") then - local container_id = cmd_line:match("^duplicate/(.+)") - create_body = dk:containers_duplicate_config({id = container_id}) or {} - if not create_body.HostConfig then create_body.HostConfig = {} end - if next(create_body) ~= nil then - default_config.name = nil - default_config.image = create_body.Image - default_config.hostname = create_body.Hostname - default_config.tty = create_body.Tty and true or false - default_config.interactive = create_body.OpenStdin and true or false - default_config.privileged = create_body.HostConfig.Privileged and true or false - default_config.restart = create_body.HostConfig.RestartPolicy and create_body.HostConfig.RestartPolicy.name or nil - -- default_config.network = create_body.HostConfig.NetworkMode == "default" and "bridge" or create_body.HostConfig.NetworkMode - -- if container has leave original network, and add new network, .HostConfig.NetworkMode is INcorrect, so using first child of .NetworkingConfig.EndpointsConfig - default_config.network = create_body.NetworkingConfig and create_body.NetworkingConfig.EndpointsConfig and next(create_body.NetworkingConfig.EndpointsConfig) or nil - default_config.ip = default_config.network and default_config.network ~= "bridge" and default_config.network ~= "host" and default_config.network ~= "null" and create_body.NetworkingConfig.EndpointsConfig[default_config.network].IPAMConfig and create_body.NetworkingConfig.EndpointsConfig[default_config.network].IPAMConfig.IPv4Address or nil - default_config.link = create_body.HostConfig.Links - default_config.env = create_body.Env - default_config.dns = create_body.HostConfig.Dns - default_config.volume = create_body.HostConfig.Binds - default_config.cap_add = create_body.HostConfig.CapAdd - default_config.publish_all = create_body.HostConfig.PublishAllPorts - - if create_body.HostConfig.Sysctls and type(create_body.HostConfig.Sysctls) == "table" then - default_config.sysctl = {} - for k, v in pairs(create_body.HostConfig.Sysctls) do - table.insert( default_config.sysctl, k.."="..v ) - end - end - - if create_body.HostConfig.LogConfig and create_body.HostConfig.LogConfig.Config and type(create_body.HostConfig.LogConfig.Config) == "table" then - default_config.log_opt = {} - for k, v in pairs(create_body.HostConfig.LogConfig.Config) do - table.insert( default_config.log_opt, k.."="..v ) - end - end - - if create_body.HostConfig.PortBindings and type(create_body.HostConfig.PortBindings) == "table" then - default_config.publish = {} - for k, v in pairs(create_body.HostConfig.PortBindings) do - table.insert( default_config.publish, v[1].HostPort..":"..k:match("^(%d+)/.+").."/"..k:match("^%d+/(.+)") ) - end - end - - default_config.user = create_body.User or nil - default_config.command = create_body.Cmd and type(create_body.Cmd) == "table" and table.concat(create_body.Cmd, " ") or nil - default_config.advance = 1 - default_config.cpus = create_body.HostConfig.NanoCPUs - default_config.cpu_shares = create_body.HostConfig.CpuShares - default_config.memory = create_body.HostConfig.Memory - default_config.blkio_weight = create_body.HostConfig.BlkioWeight - - if create_body.HostConfig.Devices and type(create_body.HostConfig.Devices) == "table" then - default_config.device = {} - for _, v in ipairs(create_body.HostConfig.Devices) do - table.insert( default_config.device, v.PathOnHost..":"..v.PathInContainer..(v.CgroupPermissions ~= "" and (":" .. v.CgroupPermissions) or "") ) - end - end - if create_body.HostConfig.Tmpfs and type(create_body.HostConfig.Tmpfs) == "table" then - default_config.tmpfs = {} - for k, v in pairs(create_body.HostConfig.Tmpfs) do - table.insert( default_config.tmpfs, k .. (v~="" and ":" or "")..v ) - end - end - end + local container_id = cmd_line:match("^duplicate/(.+)") + create_body = dk:containers_duplicate_config({id = container_id}) or {} + + if not create_body.HostConfig then + create_body.HostConfig = {} + end + + if next(create_body) ~= nil then + default_config.name = nil + default_config.image = create_body.Image + default_config.hostname = create_body.Hostname + default_config.tty = create_body.Tty and true or false + default_config.interactive = create_body.OpenStdin and true or false + default_config.privileged = create_body.HostConfig.Privileged and true or false + default_config.restart = create_body.HostConfig.RestartPolicy and create_body.HostConfig.RestartPolicy.name or nil + default_config.network = create_body.NetworkingConfig and create_body.NetworkingConfig.EndpointsConfig and next(create_body.NetworkingConfig.EndpointsConfig) or nil + default_config.ip = default_config.network and default_config.network ~= "bridge" and default_config.network ~= "host" and default_config.network ~= "null" and create_body.NetworkingConfig.EndpointsConfig[default_config.network].IPAMConfig and create_body.NetworkingConfig.EndpointsConfig[default_config.network].IPAMConfig.IPv4Address or nil + default_config.link = create_body.HostConfig.Links + default_config.env = create_body.Env + default_config.dns = create_body.HostConfig.Dns + default_config.volume = create_body.HostConfig.Binds + default_config.cap_add = create_body.HostConfig.CapAdd + default_config.publish_all = create_body.HostConfig.PublishAllPorts + + if create_body.HostConfig.Sysctls and type(create_body.HostConfig.Sysctls) == "table" then + default_config.sysctl = {} + for k, v in pairs(create_body.HostConfig.Sysctls) do + table.insert( default_config.sysctl, k.."="..v ) + end + end + + if create_body.HostConfig.LogConfig and create_body.HostConfig.LogConfig.Config and type(create_body.HostConfig.LogConfig.Config) == "table" then + default_config.log_opt = {} + for k, v in pairs(create_body.HostConfig.LogConfig.Config) do + table.insert( default_config.log_opt, k.."="..v ) + end + end + + if create_body.HostConfig.PortBindings and type(create_body.HostConfig.PortBindings) == "table" then + default_config.publish = {} + for k, v in pairs(create_body.HostConfig.PortBindings) do + table.insert( default_config.publish, v[1].HostPort..":"..k:match("^(%d+)/.+").."/"..k:match("^%d+/(.+)") ) + end + end + + default_config.user = create_body.User or nil + default_config.command = create_body.Cmd and type(create_body.Cmd) == "table" and table.concat(create_body.Cmd, " ") or nil + default_config.advance = 1 + default_config.cpus = create_body.HostConfig.NanoCPUs + default_config.cpu_shares = create_body.HostConfig.CpuShares + default_config.memory = create_body.HostConfig.Memory + default_config.blkio_weight = create_body.HostConfig.BlkioWeight + + if create_body.HostConfig.Devices and type(create_body.HostConfig.Devices) == "table" then + default_config.device = {} + for _, v in ipairs(create_body.HostConfig.Devices) do + table.insert( default_config.device, v.PathOnHost..":"..v.PathInContainer..(v.CgroupPermissions ~= "" and (":" .. v.CgroupPermissions) or "") ) + end + end + + if create_body.HostConfig.Tmpfs and type(create_body.HostConfig.Tmpfs) == "table" then + default_config.tmpfs = {} + for k, v in pairs(create_body.HostConfig.Tmpfs) do + table.insert( default_config.tmpfs, k .. (v~="" and ":" or "")..v ) + end + end + end end -local m = SimpleForm("docker", translate("Docker")) +m = SimpleForm("docker", translate("Docker")) m.redirect = luci.dispatcher.build_url("admin", "docker", "containers") --- m.reset = false --- m.submit = false --- new Container -docker_status = m:section(SimpleSection) -docker_status.template = "dockerman/apply_widget" -docker_status.err=docker:read_status() -docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" "," ") -if docker_status.err then docker:clear_status() end +s = m:section(SimpleSection) +s.template = "dockerman/apply_widget" +s.err=docker:read_status() +s.err=s.err and s.err:gsub("\n","<br>"):gsub(" "," ") +if s.err then + docker:clear_status() +end -local s = m:section(SimpleSection, translate("New Container")) +s = m:section(SimpleSection, translate("New Container")) s.addremove = true s.anonymous = true -local d = s:option(DummyValue,"cmd_line", translate("Resolve CLI")) -d.rawhtml = true -d.template = "dockerman/newcontainer_resolve" - -d = s:option(Value, "name", translate("Container Name")) -d.rmempty = true -d.default = default_config.name or nil - -d = s:option(Flag, "interactive", translate("Interactive (-i)")) -d.rmempty = true -d.disabled = 0 -d.enabled = 1 -d.default = default_config.interactive and 1 or 0 - -d = s:option(Flag, "tty", translate("TTY (-t)")) -d.rmempty = true -d.disabled = 0 -d.enabled = 1 -d.default = default_config.tty and 1 or 0 - -d = s:option(Value, "image", translate("Docker Image")) -d.rmempty = true -d.default = default_config.image or nil +o = s:option(DummyValue,"cmd_line", translate("Resolve CLI")) +o.rawhtml = true +o.template = "dockerman/newcontainer_resolve" + +o = s:option(Value, "name", translate("Container Name")) +o.rmempty = true +o.default = default_config.name or nil + +o = s:option(Flag, "interactive", translate("Interactive (-i)")) +o.rmempty = true +o.disabled = 0 +o.enabled = 1 +o.default = default_config.interactive and 1 or 0 + +o = s:option(Flag, "tty", translate("TTY (-t)")) +o.rmempty = true +o.disabled = 0 +o.enabled = 1 +o.default = default_config.tty and 1 or 0 + +o = s:option(Value, "image", translate("Docker Image")) +o.rmempty = true +o.default = default_config.image or nil for _, v in ipairs (images) do - if v.RepoTags then - d:value(v.RepoTags[1], v.RepoTags[1]) - end + if v.RepoTags then + o:value(v.RepoTags[1], v.RepoTags[1]) + end end -d = s:option(Flag, "_force_pull", translate("Always pull image first")) -d.rmempty = true -d.disabled = 0 -d.enabled = 1 -d.default = 0 - -d = s:option(Flag, "privileged", translate("Privileged")) -d.rmempty = true -d.disabled = 0 -d.enabled = 1 -d.default = default_config.privileged and 1 or 0 - -d = s:option(ListValue, "restart", translate("Restart Policy")) -d.rmempty = true - -d:value("no", "No") -d:value("unless-stopped", "Unless stopped") -d:value("always", "Always") -d:value("on-failure", "On failure") -d.default = default_config.restart or "unless-stopped" +o = s:option(Flag, "_force_pull", translate("Always pull image first")) +o.rmempty = true +o.disabled = 0 +o.enabled = 1 +o.default = 0 + +o = s:option(Flag, "privileged", translate("Privileged")) +o.rmempty = true +o.disabled = 0 +o.enabled = 1 +o.default = default_config.privileged and 1 or 0 + +o = s:option(ListValue, "restart", translate("Restart Policy")) +o.rmempty = true +o:value("no", "No") +o:value("unless-stopped", "Unless stopped") +o:value("always", "Always") +o:value("on-failure", "On failure") +o.default = default_config.restart or "unless-stopped" local d_network = s:option(ListValue, "network", translate("Networks")) d_network.rmempty = true @@ -305,349 +509,394 @@ d_ip.datatype="ip4addr" d_ip:depends("network", "nil") d_ip.default = default_config.ip or nil -d = s:option(DynamicList, "link", translate("Links with other containers")) -d.placeholder = "container_name:alias" -d.rmempty = true -d:depends("network", "bridge") -d.default = default_config.link or nil - -d = s:option(DynamicList, "dns", translate("Set custom DNS servers")) -d.placeholder = "8.8.8.8" -d.rmempty = true -d.default = default_config.dns or nil - -d = s:option(Value, "user", translate("User(-u)"), translate("The user that commands are run as inside the container.(format: name|uid[:group|gid])")) -d.placeholder = "1000:1000" -d.rmempty = true -d.default = default_config.user or nil - -d = s:option(DynamicList, "env", translate("Environmental Variable(-e)"), translate("Set environment variables to inside the container")) -d.placeholder = "TZ=Asia/Shanghai" -d.rmempty = true -d.default = default_config.env or nil - -d = s:option(DynamicList, "volume", translate("Bind Mount(-v)"), translate("Bind mount a volume")) -d.placeholder = "/media:/media:slave" -d.rmempty = true -d.default = default_config.volume or nil - -local d_publish = s:option(DynamicList, "publish", translate("Exposed Ports(-p)"), translate("Publish container's port(s) to the host")) +o = s:option(DynamicList, "link", translate("Links with other containers")) +o.placeholder = "container_name:alias" +o.rmempty = true +o:depends("network", "bridge") +o.default = default_config.link or nil + +o = s:option(DynamicList, "dns", translate("Set custom DNS servers")) +o.placeholder = "8.8.8.8" +o.rmempty = true +o.default = default_config.dns or nil + +o = s:option(Value, "user", + translate("User(-u)"), + translate("The user that commands are run as inside the container.(format: name|uid[:group|gid])")) +o.placeholder = "1000:1000" +o.rmempty = true +o.default = default_config.user or nil + +o = s:option(DynamicList, "env", + translate("Environmental Variable(-e)"), + translate("Set environment variables to inside the container")) +o.placeholder = "TZ=Asia/Shanghai" +o.rmempty = true +o.default = default_config.env or nil + +o = s:option(DynamicList, "volume", + translate("Bind Mount(-v)"), + translate("Bind mount a volume")) +o.placeholder = "/media:/media:slave" +o.rmempty = true +o.default = default_config.volume or nil + +local d_publish = s:option(DynamicList, "publish", + translate("Exposed Ports(-p)"), + translate("Publish container's port(s) to the host")) d_publish.placeholder = "2200:22/tcp" d_publish.rmempty = true d_publish.default = default_config.publish or nil -d = s:option(Value, "command", translate("Run command")) -d.placeholder = "/bin/sh init.sh" -d.rmempty = true -d.default = default_config.command or nil - -d = s:option(Flag, "advance", translate("Advance")) -d.rmempty = true -d.disabled = 0 -d.enabled = 1 -d.default = default_config.advance or 0 - -d = s:option(Value, "hostname", translate("Host Name"), translate("The hostname to use for the container")) -d.rmempty = true -d.default = default_config.hostname or nil -d:depends("advance", 1) - -d = s:option(Flag, "publish_all", translate("Exposed All Ports(-P)"), translate("Allocates an ephemeral host port for all of a container's exposed ports")) -d.rmempty = true -d.disabled = 0 -d.enabled = 1 -d.default = default_config.publish_all and 1 or 0 -d:depends("advance", 1) - -d = s:option(DynamicList, "device", translate("Device(--device)"), translate("Add host device to the container")) -d.placeholder = "/dev/sda:/dev/xvdc:rwm" -d.rmempty = true -d:depends("advance", 1) -d.default = default_config.device or nil - -d = s:option(DynamicList, "tmpfs", translate("Tmpfs(--tmpfs)"), translate("Mount tmpfs directory")) -d.placeholder = "/run:rw,noexec,nosuid,size=65536k" -d.rmempty = true -d:depends("advance", 1) -d.default = default_config.tmpfs or nil - -d = s:option(DynamicList, "sysctl", translate("Sysctl(--sysctl)"), translate("Sysctls (kernel parameters) options")) -d.placeholder = "net.ipv4.ip_forward=1" -d.rmempty = true -d:depends("advance", 1) -d.default = default_config.sysctl or nil - -d = s:option(DynamicList, "cap_add", translate("CAP-ADD(--cap-add)"), translate("A list of kernel capabilities to add to the container")) -d.placeholder = "NET_ADMIN" -d.rmempty = true -d:depends("advance", 1) -d.default = default_config.cap_add or nil - -d = s:option(Value, "cpus", translate("CPUs"), translate("Number of CPUs. Number is a fractional number. 0.000 means no limit")) -d.placeholder = "1.5" -d.rmempty = true -d:depends("advance", 1) -d.datatype="ufloat" -d.default = default_config.cpus or nil - -d = s:option(Value, "cpu_shares", translate("CPU Shares Weight"), translate("CPU shares relative weight, if 0 is set, the system will ignore the value and use the default of 1024")) -d.placeholder = "1024" -d.rmempty = true -d:depends("advance", 1) -d.datatype="uinteger" -d.default = default_config.cpu_shares or nil - -d = s:option(Value, "memory", translate("Memory"), translate("Memory limit (format: <number>[<unit>]). Number is a positive integer. Unit can be one of b, k, m, or g. Minimum is 4M")) -d.placeholder = "128m" -d.rmempty = true -d:depends("advance", 1) -d.default = default_config.memory or nil - -d = s:option(Value, "blkio_weight", translate("Block IO Weight"), translate("Block IO weight (relative weight) accepts a weight value between 10 and 1000")) -d.placeholder = "500" -d.rmempty = true -d:depends("advance", 1) -d.datatype="uinteger" -d.default = default_config.blkio_weight or nil - -d = s:option(DynamicList, "log_opt", translate("Log driver options"), translate("The logging configuration for this container")) -d.placeholder = "max-size=1m" -d.rmempty = true -d:depends("advance", 1) -d.default = default_config.log_opt or nil +o = s:option(Value, "command", translate("Run command")) +o.placeholder = "/bin/sh init.sh" +o.rmempty = true +o.default = default_config.command or nil + +o = s:option(Flag, "advance", translate("Advance")) +o.rmempty = true +o.disabled = 0 +o.enabled = 1 +o.default = default_config.advance or 0 + +o = s:option(Value, "hostname", + translate("Host Name"), + translate("The hostname to use for the container")) +o.rmempty = true +o.default = default_config.hostname or nil +o:depends("advance", 1) + +o = s:option(Flag, "publish_all", + translate("Exposed All Ports(-P)"), + translate("Allocates an ephemeral host port for all of a container's exposed ports")) +o.rmempty = true +o.disabled = 0 +o.enabled = 1 +o.default = default_config.publish_all and 1 or 0 +o:depends("advance", 1) + +o = s:option(DynamicList, "device", + translate("Device(--device)"), + translate("Add host device to the container")) +o.placeholder = "/dev/sda:/dev/xvdc:rwm" +o.rmempty = true +o:depends("advance", 1) +o.default = default_config.device or nil + +o = s:option(DynamicList, "tmpfs", + translate("Tmpfs(--tmpfs)"), + translate("Mount tmpfs directory")) +o.placeholder = "/run:rw,noexec,nosuid,size=65536k" +o.rmempty = true +o:depends("advance", 1) +o.default = default_config.tmpfs or nil + +o = s:option(DynamicList, "sysctl", + translate("Sysctl(--sysctl)"), + translate("Sysctls (kernel parameters) options")) +o.placeholder = "net.ipv4.ip_forward=1" +o.rmempty = true +o:depends("advance", 1) +o.default = default_config.sysctl or nil + +o = s:option(DynamicList, "cap_add", + translate("CAP-ADD(--cap-add)"), + translate("A list of kernel capabilities to add to the container")) +o.placeholder = "NET_ADMIN" +o.rmempty = true +o:depends("advance", 1) +o.default = default_config.cap_add or nil + +o = s:option(Value, "cpus", + translate("CPUs"), + translate("Number of CPUs. Number is a fractional number. 0.000 means no limit")) +o.placeholder = "1.5" +o.rmempty = true +o:depends("advance", 1) +o.datatype="ufloat" +o.default = default_config.cpus or nil + +o = s:option(Value, "cpu_shares", + translate("CPU Shares Weight"), + translate("CPU shares relative weight, if 0 is set, the system will ignore the value and use the default of 1024")) +o.placeholder = "1024" +o.rmempty = true +o:depends("advance", 1) +o.datatype="uinteger" +o.default = default_config.cpu_shares or nil + +o = s:option(Value, "memory", + translate("Memory"), + translate("Memory limit (format: <number>[<unit>]). Number is a positive integer. Unit can be one of b, k, m, or g. Minimum is 4M")) +o.placeholder = "128m" +o.rmempty = true +o:depends("advance", 1) +o.default = default_config.memory or nil + +o = s:option(Value, "blkio_weight", + translate("Block IO Weight"), + translate("Block IO weight (relative weight) accepts a weight value between 10 and 1000")) +o.placeholder = "500" +o.rmempty = true +o:depends("advance", 1) +o.datatype="uinteger" +o.default = default_config.blkio_weight or nil + +o = s:option(DynamicList, "log_opt", + translate("Log driver options"), + translate("The logging configuration for this container")) +o.placeholder = "max-size=1m" +o.rmempty = true +o:depends("advance", 1) +o.default = default_config.log_opt or nil for _, v in ipairs (networks) do - if v.Name then - local parent = v.Options and v.Options.parent or nil - local ip = v.IPAM and v.IPAM.Config and v.IPAM.Config[1] and v.IPAM.Config[1].Subnet or nil - ipv6 = v.IPAM and v.IPAM.Config and v.IPAM.Config[2] and v.IPAM.Config[2].Subnet or nil - local network_name = v.Name .. " | " .. v.Driver .. (parent and (" | " .. parent) or "") .. (ip and (" | " .. ip) or "").. (ipv6 and (" | " .. ipv6) or "") - d_network:value(v.Name, network_name) - - if v.Name ~= "none" and v.Name ~= "bridge" and v.Name ~= "host" then - d_ip:depends("network", v.Name) - end - - if v.Driver == "bridge" then - d_publish:depends("network", v.Name) - end - end + if v.Name then + local parent = v.Options and v.Options.parent or nil + local ip = v.IPAM and v.IPAM.Config and v.IPAM.Config[1] and v.IPAM.Config[1].Subnet or nil + ipv6 = v.IPAM and v.IPAM.Config and v.IPAM.Config[2] and v.IPAM.Config[2].Subnet or nil + local network_name = v.Name .. " | " .. v.Driver .. (parent and (" | " .. parent) or "") .. (ip and (" | " .. ip) or "").. (ipv6 and (" | " .. ipv6) or "") + d_network:value(v.Name, network_name) + + if v.Name ~= "none" and v.Name ~= "bridge" and v.Name ~= "host" then + d_ip:depends("network", v.Name) + end + + if v.Driver == "bridge" then + d_publish:depends("network", v.Name) + end + end end m.handle = function(self, state, data) - if state ~= FORM_VALID then return end - local tmp - local name = data.name or ("luci_" .. os.date("%Y%m%d%H%M%S")) - local hostname = data.hostname - local tty = type(data.tty) == "number" and (data.tty == 1 and true or false) or default_config.tty or false - local publish_all = type(data.publish_all) == "number" and (data.publish_all == 1 and true or false) or default_config.publish_all or false - local interactive = type(data.interactive) == "number" and (data.interactive == 1 and true or false) or default_config.interactive or false - local image = data.image - local user = data.user - if image and not image:match(".-:.+") then - image = image .. ":latest" - end - local privileged = type(data.privileged) == "number" and (data.privileged == 1 and true or false) or default_config.privileged or false - local restart = data.restart - local env = data.env - local dns = data.dns - local cap_add = data.cap_add - local sysctl = {} - tmp = data.sysctl - if type(tmp) == "table" then - for i, v in ipairs(tmp) do - local k,v1 = v:match("(.-)=(.+)") - if k and v1 then - sysctl[k]=v1 - end - end - end - local log_opt = {} - tmp = data.log_opt - if type(tmp) == "table" then - for i, v in ipairs(tmp) do - local k,v1 = v:match("(.-)=(.+)") - if k and v1 then - log_opt[k]=v1 - end - end - end - local network = data.network - local ip = (network ~= "bridge" and network ~= "host" and network ~= "none") and data.ip or nil - local volume = data.volume - local memory = data.memory or 0 - local cpu_shares = data.cpu_shares or 0 - local cpus = data.cpus or 0 - local blkio_weight = data.blkio_weight or 500 - - local portbindings = {} - local exposedports = {} - local tmpfs = {} - tmp = data.tmpfs - if type(tmp) == "table" then - for i, v in ipairs(tmp)do - local k= v:match("([^:]+)") - local v1 = v:match(".-:([^:]+)") or "" - if k then - tmpfs[k]=v1 - end - end - end - - local device = {} - tmp = data.device - if type(tmp) == "table" then - for i, v in ipairs(tmp) do - local t = {} - local _,_, h, c, p = v:find("(.-):(.-):(.+)") - if h and c then - t['PathOnHost'] = h - t['PathInContainer'] = c - t['CgroupPermissions'] = p or "rwm" - else - local _,_, h, c = v:find("(.-):(.+)") - if h and c then - t['PathOnHost'] = h - t['PathInContainer'] = c - t['CgroupPermissions'] = "rwm" - else - t['PathOnHost'] = v - t['PathInContainer'] = v - t['CgroupPermissions'] = "rwm" - end - end - if next(t) ~= nil then - table.insert( device, t ) - end - end - end - - tmp = data.publish or {} - for i, v in ipairs(tmp) do - for v1 ,v2 in string.gmatch(v, "(%d+):([^%s]+)") do - local _,_,p= v2:find("^%d+/(%w+)") - if p == nil then - v2=v2..'/tcp' - end - portbindings[v2] = {{HostPort=v1}} - exposedports[v2] = {HostPort=v1} - end - end - - local link = data.link - tmp = data.command - local command = {} - if tmp ~= nil then - for v in string.gmatch(tmp, "[^%s]+") do - command[#command+1] = v - end - end - if memory ~= 0 then - _,_,n,unit = memory:find("([%d%.]+)([%l%u]+)") - if n then - unit = unit and unit:sub(1,1):upper() or "B" - if unit == "M" then - memory = tonumber(n) * 1024 * 1024 - elseif unit == "G" then - memory = tonumber(n) * 1024 * 1024 * 1024 - elseif unit == "K" then - memory = tonumber(n) * 1024 - else - memory = tonumber(n) - end - end - end - - create_body.Hostname = network ~= "host" and (hostname or name) or nil - create_body.Tty = tty and true or false - create_body.OpenStdin = interactive and true or false - create_body.User = user - create_body.Cmd = command - create_body.Env = env - create_body.Image = image - create_body.ExposedPorts = exposedports - create_body.HostConfig = create_body.HostConfig or {} - create_body.HostConfig.Dns = dns - create_body.HostConfig.Binds = volume - create_body.HostConfig.RestartPolicy = { Name = restart, MaximumRetryCount = 0 } - create_body.HostConfig.Privileged = privileged and true or false - create_body.HostConfig.PortBindings = portbindings - create_body.HostConfig.Memory = tonumber(memory) - create_body.HostConfig.CpuShares = tonumber(cpu_shares) - create_body.HostConfig.NanoCPUs = tonumber(cpus) * 10 ^ 9 - create_body.HostConfig.BlkioWeight = tonumber(blkio_weight) - create_body.HostConfig.PublishAllPorts = publish_all - if create_body.HostConfig.NetworkMode ~= network then - -- network mode changed, need to clear duplicate config - create_body.NetworkingConfig = nil - end - create_body.HostConfig.NetworkMode = network - if ip then - if create_body.NetworkingConfig and create_body.NetworkingConfig.EndpointsConfig and type(create_body.NetworkingConfig.EndpointsConfig) == "table" then - -- ip + duplicate config - for k, v in pairs (create_body.NetworkingConfig.EndpointsConfig) do - if k == network and v.IPAMConfig and v.IPAMConfig.IPv4Address then - v.IPAMConfig.IPv4Address = ip - else - create_body.NetworkingConfig.EndpointsConfig = { [network] = { IPAMConfig = { IPv4Address = ip } } } - end - break - end - else - -- ip + no duplicate config - create_body.NetworkingConfig = { EndpointsConfig = { [network] = { IPAMConfig = { IPv4Address = ip } } } } - end - elseif not create_body.NetworkingConfig then - -- no ip + no duplicate config - create_body.NetworkingConfig = nil - end - create_body["HostConfig"]["Tmpfs"] = tmpfs - create_body["HostConfig"]["Devices"] = device - create_body["HostConfig"]["Sysctls"] = sysctl - create_body["HostConfig"]["CapAdd"] = cap_add - create_body["HostConfig"]["LogConfig"] = next(log_opt) ~= nil and { Config = log_opt } or nil - - if network == "bridge" then - create_body["HostConfig"]["Links"] = link - end - local pull_image = function(image) - local json_stringify = luci.jsonc and luci.jsonc.stringify - docker:append_status("Images: " .. "pulling" .. " " .. image .. "...\n") - local res = dk.images:create({query = {fromImage=image}}, docker.pull_image_show_status_cb) - if res and res.code == 200 and (res.body[#res.body] and not res.body[#res.body].error and res.body[#res.body].status and (res.body[#res.body].status == "Status: Downloaded newer image for ".. image or res.body[#res.body].status == "Status: Image is up to date for ".. image)) then - docker:append_status("done\n") - else - res.code = (res.code == 200) and 500 or res.code - docker:append_status("code:" .. res.code.." ".. (res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message)).. "\n") - luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer")) - end - end - docker:clear_status() - local exist_image = false - if image then - for _, v in ipairs (images) do - if v.RepoTags and v.RepoTags[1] == image then - exist_image = true - break - end - end - if not exist_image then - pull_image(image) - elseif data._force_pull == 1 then - pull_image(image) - end - end - - create_body = docker.clear_empty_tables(create_body) - docker:append_status("Container: " .. "create" .. " " .. name .. "...") - local res = dk.containers:create({name = name, body = create_body}) - if res and res.code == 201 then - docker:clear_status() - luci.http.redirect(luci.dispatcher.build_url("admin/docker/containers")) - else - docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message)) - luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer")) - end + if state ~= FORM_VALID then + return + end + + local tmp + local name = data.name or ("luci_" .. os.date("%Y%m%d%H%M%S")) + local hostname = data.hostname + local tty = type(data.tty) == "number" and (data.tty == 1 and true or false) or default_config.tty or false + local publish_all = type(data.publish_all) == "number" and (data.publish_all == 1 and true or false) or default_config.publish_all or false + local interactive = type(data.interactive) == "number" and (data.interactive == 1 and true or false) or default_config.interactive or false + local image = data.image + local user = data.user + + if image and not image:match(".-:.+") then + image = image .. ":latest" + end + + local privileged = type(data.privileged) == "number" and (data.privileged == 1 and true or false) or default_config.privileged or false + local restart = data.restart + local env = data.env + local dns = data.dns + local cap_add = data.cap_add + local sysctl = {} + + tmp = data.sysctl + if type(tmp) == "table" then + for i, v in ipairs(tmp) do + local k,v1 = v:match("(.-)=(.+)") + if k and v1 then + sysctl[k]=v1 + end + end + end + + local log_opt = {} + tmp = data.log_opt + if type(tmp) == "table" then + for i, v in ipairs(tmp) do + local k,v1 = v:match("(.-)=(.+)") + if k and v1 then + log_opt[k]=v1 + end + end + end + + local network = data.network + local ip = (network ~= "bridge" and network ~= "host" and network ~= "none") and data.ip or nil + local volume = data.volume + local memory = data.memory or 0 + local cpu_shares = data.cpu_shares or 0 + local cpus = data.cpus or 0 + local blkio_weight = data.blkio_weight or 500 + + local portbindings = {} + local exposedports = {} + + local tmpfs = {} + tmp = data.tmpfs + if type(tmp) == "table" then + for i, v in ipairs(tmp)do + local k= v:match("([^:]+)") + local v1 = v:match(".-:([^:]+)") or "" + if k then + tmpfs[k]=v1 + end + end + end + + local device = {} + tmp = data.device + if type(tmp) == "table" then + for i, v in ipairs(tmp) do + local t = {} + local _,_, h, c, p = v:find("(.-):(.-):(.+)") + if h and c then + t['PathOnHost'] = h + t['PathInContainer'] = c + t['CgroupPermissions'] = p or "rwm" + else + local _,_, h, c = v:find("(.-):(.+)") + if h and c then + t['PathOnHost'] = h + t['PathInContainer'] = c + t['CgroupPermissions'] = "rwm" + else + t['PathOnHost'] = v + t['PathInContainer'] = v + t['CgroupPermissions'] = "rwm" + end + end + + if next(t) ~= nil then + table.insert( device, t ) + end + end + end + + tmp = data.publish or {} + for i, v in ipairs(tmp) do + for v1 ,v2 in string.gmatch(v, "(%d+):([^%s]+)") do + local _,_,p= v2:find("^%d+/(%w+)") + if p == nil then + v2=v2..'/tcp' + end + portbindings[v2] = {{HostPort=v1}} + exposedports[v2] = {HostPort=v1} + end + end + + local link = data.link + tmp = data.command + local command = {} + if tmp ~= nil then + for v in string.gmatch(tmp, "[^%s]+") do + command[#command+1] = v + end + end + + if memory ~= 0 then + _,_,n,unit = memory:find("([%d%.]+)([%l%u]+)") + if n then + unit = unit and unit:sub(1,1):upper() or "B" + if unit == "M" then + memory = tonumber(n) * 1024 * 1024 + elseif unit == "G" then + memory = tonumber(n) * 1024 * 1024 * 1024 + elseif unit == "K" then + memory = tonumber(n) * 1024 + else + memory = tonumber(n) + end + end + end + + create_body.Hostname = network ~= "host" and (hostname or name) or nil + create_body.Tty = tty and true or false + create_body.OpenStdin = interactive and true or false + create_body.User = user + create_body.Cmd = command + create_body.Env = env + create_body.Image = image + create_body.ExposedPorts = exposedports + create_body.HostConfig = create_body.HostConfig or {} + create_body.HostConfig.Dns = dns + create_body.HostConfig.Binds = volume + create_body.HostConfig.RestartPolicy = { Name = restart, MaximumRetryCount = 0 } + create_body.HostConfig.Privileged = privileged and true or false + create_body.HostConfig.PortBindings = portbindings + create_body.HostConfig.Memory = tonumber(memory) + create_body.HostConfig.CpuShares = tonumber(cpu_shares) + create_body.HostConfig.NanoCPUs = tonumber(cpus) * 10 ^ 9 + create_body.HostConfig.BlkioWeight = tonumber(blkio_weight) + create_body.HostConfig.PublishAllPorts = publish_all + + if create_body.HostConfig.NetworkMode ~= network then + create_body.NetworkingConfig = nil + end + + create_body.HostConfig.NetworkMode = network + + if ip then + if create_body.NetworkingConfig and create_body.NetworkingConfig.EndpointsConfig and type(create_body.NetworkingConfig.EndpointsConfig) == "table" then + for k, v in pairs (create_body.NetworkingConfig.EndpointsConfig) do + if k == network and v.IPAMConfig and v.IPAMConfig.IPv4Address then + v.IPAMConfig.IPv4Address = ip + else + create_body.NetworkingConfig.EndpointsConfig = { [network] = { IPAMConfig = { IPv4Address = ip } } } + end + break + end + else + create_body.NetworkingConfig = { EndpointsConfig = { [network] = { IPAMConfig = { IPv4Address = ip } } } } + end + elseif not create_body.NetworkingConfig then + create_body.NetworkingConfig = nil + end + + create_body["HostConfig"]["Tmpfs"] = tmpfs + create_body["HostConfig"]["Devices"] = device + create_body["HostConfig"]["Sysctls"] = sysctl + create_body["HostConfig"]["CapAdd"] = cap_add + create_body["HostConfig"]["LogConfig"] = next(log_opt) ~= nil and { Config = log_opt } or nil + + if network == "bridge" then + create_body["HostConfig"]["Links"] = link + end + + local pull_image = function(image) + local json_stringify = luci.jsonc and luci.jsonc.stringify + docker:append_status("Images: " .. "pulling" .. " " .. image .. "...\n") + local res = dk.images:create({query = {fromImage=image}}, docker.pull_image_show_status_cb) + if res and res.code == 200 and (res.body[#res.body] and not res.body[#res.body].error and res.body[#res.body].status and (res.body[#res.body].status == "Status: Downloaded newer image for ".. image or res.body[#res.body].status == "Status: Image is up to date for ".. image)) then + docker:append_status("done\n") + else + res.code = (res.code == 200) and 500 or res.code + docker:append_status("code:" .. res.code.." ".. (res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message)).. "\n") + luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer")) + end + end + + docker:clear_status() + local exist_image = false + + if image then + for _, v in ipairs (images) do + if v.RepoTags and v.RepoTags[1] == image then + exist_image = true + break + end + end + if not exist_image then + pull_image(image) + elseif data._force_pull == 1 then + pull_image(image) + end + end + + create_body = docker.clear_empty_tables(create_body) + + docker:append_status("Container: " .. "create" .. " " .. name .. "...") + local res = dk.containers:create({name = name, body = create_body}) + if res and res.code == 201 then + docker:clear_status() + luci.http.redirect(luci.dispatcher.build_url("admin/docker/containers")) + else + docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message)) + luci.http.redirect(luci.dispatcher.build_url("admin/docker/newcontainer")) + end end return m diff --git a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua index bdadbaf881..f4cfea5bc5 100644 --- a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua +++ b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/newnetwork.lua @@ -3,219 +3,244 @@ LuCI - Lua Configuration Interface Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman> ]]-- -require "luci.util" local docker = require "luci.model.docker" + +local m, s, o + local dk = docker.new() m = SimpleForm("docker", translate("Docker")) m.redirect = luci.dispatcher.build_url("admin", "docker", "networks") -docker_status = m:section(SimpleSection) -docker_status.template = "dockerman/apply_widget" -docker_status.err=docker:read_status() -docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" "," ") -if docker_status.err then docker:clear_status() end +s = m:section(SimpleSection) +s.template = "dockerman/apply_widget" +s.err=docker:read_status() +s.err=s.err and s.err:gsub("\n","<br>"):gsub(" "," ") +if s.err then + docker:clear_status() +end s = m:section(SimpleSection, translate("New Network")) s.addremove = true s.anonymous = true -d = s:option(Value, "name", translate("Network Name")) -d.rmempty = true +o = s:option(Value, "name", translate("Network Name")) +o.rmempty = true -d = s:option(ListValue, "dirver", translate("Driver")) -d.rmempty = true -d:value("bridge", "bridge") -d:value("macvlan", "macvlan") -d:value("ipvlan", "ipvlan") -d:value("overlay", "overlay") +o = s:option(ListValue, "dirver", translate("Driver")) +o.rmempty = true +o:value("bridge", "bridge") +o:value("macvlan", "macvlan") +o:value("ipvlan", "ipvlan") +o:value("overlay", "overlay") -d = s:option(Value, "parent", translate("Parent Interface")) -d.rmempty = true -d:depends("dirver", "macvlan") +o = s:option(Value, "parent", translate("Parent Interface")) +o.rmempty = true +o:depends("dirver", "macvlan") local interfaces = luci.sys and luci.sys.net and luci.sys.net.devices() or {} for _, v in ipairs(interfaces) do - d:value(v, v) + o:value(v, v) end -d.default="br-lan" -d.placeholder="br-lan" - -d = s:option(Value, "macvlan_mode", translate("Macvlan Mode")) -d.rmempty = true -d:depends("dirver", "macvlan") -d.default="bridge" -d:value("bridge", "bridge") -d:value("private", "private") -d:value("vepa", "vepa") -d:value("passthru", "passthru") - -d = s:option(Value, "ipvlan_mode", translate("Ipvlan Mode")) -d.rmempty = true -d:depends("dirver", "ipvlan") -d.default="l3" -d:value("l2", "l2") -d:value("l3", "l3") - -d = s:option(Flag, "ingress", translate("Ingress"), translate("Ingress network is the network which provides the routing-mesh in swarm mode")) -d.rmempty = true -d.disabled = 0 -d.enabled = 1 -d.default = 0 -d:depends("dirver", "overlay") - -d = s:option(DynamicList, "options", translate("Options")) -d.rmempty = true -d.placeholder="com.docker.network.driver.mtu=1500" - -d = s:option(Flag, "internal", translate("Internal"), translate("Restrict external access to the network")) -d.rmempty = true -d:depends("dirver", "overlay") -d.disabled = 0 -d.enabled = 1 -d.default = 0 - -if nixio.fs.access("/etc/config/network") and nixio.fs.access("/etc/config/firewall")then - d = s:option(Flag, "op_macvlan", translate("Create macvlan interface"), translate("Auto create macvlan interface in Openwrt")) - d:depends("dirver", "macvlan") - d.disabled = 0 - d.enabled = 1 - d.default = 1 +o.default="br-lan" +o.placeholder="br-lan" + +o = s:option(Value, "macvlan_mode", translate("Macvlan Mode")) +o.rmempty = true +o:depends("dirver", "macvlan") +o.default="bridge" +o:value("bridge", "bridge") +o:value("private", "private") +o:value("vepa", "vepa") +o:value("passthru", "passthru") + +o = s:option(Value, "ipvlan_mode", translate("Ipvlan Mode")) +o.rmempty = true +o:depends("dirver", "ipvlan") +o.default="l3" +o:value("l2", "l2") +o:value("l3", "l3") + +o = s:option(Flag, "ingress", + translate("Ingress"), + translate("Ingress network is the network which provides the routing-mesh in swarm mode")) +o.rmempty = true +o.disabled = 0 +o.enabled = 1 +o.default = 0 +o:depends("dirver", "overlay") + +o = s:option(DynamicList, "options", translate("Options")) +o.rmempty = true +o.placeholder="com.docker.network.driver.mtu=1500" + +o = s:option(Flag, "internal", translate("Internal"), translate("Restrict external access to the network")) +o.rmempty = true +o:depends("dirver", "overlay") +o.disabled = 0 +o.enabled = 1 +o.default = 0 + +if nixio.fs.access("/etc/config/network") and nixio.fs.access("/etc/config/firewall")then + o = s:option(Flag, "op_macvlan", translate("Create macvlan interface"), translate("Auto create macvlan interface in Openwrt")) + o:depends("dirver", "macvlan") + o.disabled = 0 + o.enabled = 1 + o.default = 1 end -d = s:option(Value, "subnet", translate("Subnet")) -d.rmempty = true -d.placeholder="10.1.0.0/16" -d.datatype="ip4addr" - -d = s:option(Value, "gateway", translate("Gateway")) -d.rmempty = true -d.placeholder="10.1.1.1" -d.datatype="ip4addr" - -d = s:option(Value, "ip_range", translate("IP range")) -d.rmempty = true -d.placeholder="10.1.1.0/24" -d.datatype="ip4addr" - -d = s:option(DynamicList, "aux_address", translate("Exclude IPs")) -d.rmempty = true -d.placeholder="my-route=10.1.1.1" - -d = s:option(Flag, "ipv6", translate("Enable IPv6")) -d.rmempty = true -d.disabled = 0 -d.enabled = 1 -d.default = 0 - -d = s:option(Value, "subnet6", translate("IPv6 Subnet")) -d.rmempty = true -d.placeholder="fe80::/10" -d.datatype="ip6addr" -d:depends("ipv6", 1) - -d = s:option(Value, "gateway6", translate("IPv6 Gateway")) -d.rmempty = true -d.placeholder="fe80::1" -d.datatype="ip6addr" -d:depends("ipv6", 1) +o = s:option(Value, "subnet", translate("Subnet")) +o.rmempty = true +o.placeholder="10.1.0.0/16" +o.datatype="ip4addr" + +o = s:option(Value, "gateway", translate("Gateway")) +o.rmempty = true +o.placeholder="10.1.1.1" +o.datatype="ip4addr" + +o = s:option(Value, "ip_range", translate("IP range")) +o.rmempty = true +o.placeholder="10.1.1.0/24" +o.datatype="ip4addr" + +o = s:option(DynamicList, "aux_address", translate("Exclude IPs")) +o.rmempty = true +o.placeholder="my-route=10.1.1.1" + +o = s:option(Flag, "ipv6", translate("Enable IPv6")) +o.rmempty = true +o.disabled = 0 +o.enabled = 1 +o.default = 0 + +o = s:option(Value, "subnet6", translate("IPv6 Subnet")) +o.rmempty = true +o.placeholder="fe80::/10" +o.datatype="ip6addr" +o:depends("ipv6", 1) + +o = s:option(Value, "gateway6", translate("IPv6 Gateway")) +o.rmempty = true +o.placeholder="fe80::1" +o.datatype="ip6addr" +o:depends("ipv6", 1) m.handle = function(self, state, data) - if state == FORM_VALID then - local name = data.name - local driver = data.dirver - - local internal = data.internal == 1 and true or false - - local subnet = data.subnet - local gateway = data.gateway - local ip_range = data.ip_range - - local aux_address = {} - local tmp = data.aux_address or {} - for i,v in ipairs(tmp) do - _,_,k1,v1 = v:find("(.-)=(.+)") - aux_address[k1] = v1 - end - - local options = {} - tmp = data.options or {} - for i,v in ipairs(tmp) do - _,_,k1,v1 = v:find("(.-)=(.+)") - options[k1] = v1 - end - - local ipv6 = data.ipv6 == 1 and true or false - - local create_body={ - Name = name, - Driver = driver, - EnableIPv6 = ipv6, - IPAM = { - Driver= "default" - }, - Internal = internal - } - - if subnet or gateway or ip_range then - create_body["IPAM"]["Config"] = { - { - Subnet = subnet, - Gateway = gateway, - IPRange = ip_range, - AuxAddress = aux_address, - AuxiliaryAddresses = aux_address - } - } - end - if driver == "macvlan" then - create_body["Options"] = { - macvlan_mode = data.macvlan_mode, - parent = data.parent - } - elseif driver == "ipvlan" then - create_body["Options"] = { - ipvlan_mode = data.ipvlan_mode - } - elseif driver == "overlay" then - create_body["Ingress"] = data.ingerss == 1 and true or false - end - - if ipv6 and data.subnet6 and data.subnet6 then - if type(create_body["IPAM"]["Config"]) ~= "table" then - create_body["IPAM"]["Config"] = {} - end - local index = #create_body["IPAM"]["Config"] - create_body["IPAM"]["Config"][index+1] = { - Subnet = data.subnet6, - Gateway = data.gateway6 - } - end - - if next(options) ~= nil then - create_body["Options"] = create_body["Options"] or {} - for k, v in pairs(options) do - create_body["Options"][k] = v - end - end - - create_body = docker.clear_empty_tables(create_body) - docker:write_status("Network: " .. "create" .. " " .. create_body.Name .. "...") - local res = dk.networks:create({body = create_body}) - if res and res.code == 201 then - docker:write_status("Network: " .. "create macvlan interface...") - res = dk.networks:inspect({ name = create_body.Name }) - if driver == "macvlan" and data.op_macvlan ~= 0 and res.code == 200 - and res.body and res.body.IPAM and res.body.IPAM.Config and res.body.IPAM.Config[1] - and res.body.IPAM.Config[1].Gateway and res.body.IPAM.Config[1].Subnet then - docker.create_macvlan_interface(data.name, data.parent, res.body.IPAM.Config[1].Gateway, res.body.IPAM.Config[1].Subnet) - end - docker:clear_status() - luci.http.redirect(luci.dispatcher.build_url("admin/docker/networks")) - else - docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "\n") - luci.http.redirect(luci.dispatcher.build_url("admin/docker/newnetwork")) - end - end + if state == FORM_VALID then + local name = data.name + local driver = data.dirver + + local internal = data.internal == 1 and true or false + + local subnet = data.subnet + local gateway = data.gateway + local ip_range = data.ip_range + + local aux_address = {} + local tmp = data.aux_address or {} + for i,v in ipairs(tmp) do + _,_,k1,v1 = v:find("(.-)=(.+)") + aux_address[k1] = v1 + end + + local options = {} + tmp = data.options or {} + for i,v in ipairs(tmp) do + _,_,k1,v1 = v:find("(.-)=(.+)") + options[k1] = v1 + end + + local ipv6 = data.ipv6 == 1 and true or false + + local create_body = { + Name = name, + Driver = driver, + EnableIPv6 = ipv6, + IPAM = { + Driver= "default" + }, + Internal = internal + } + + if subnet or gateway or ip_range then + create_body["IPAM"]["Config"] = { + { + Subnet = subnet, + Gateway = gateway, + IPRange = ip_range, + AuxAddress = aux_address, + AuxiliaryAddresses = aux_address + } + } + end + + if driver == "macvlan" then + create_body["Options"] = { + macvlan_mode = data.macvlan_mode, + parent = data.parent + } + elseif driver == "ipvlan" then + create_body["Options"] = { + ipvlan_mode = data.ipvlan_mode + } + elseif driver == "overlay" then + create_body["Ingress"] = data.ingerss == 1 and true or false + end + + if ipv6 and data.subnet6 and data.subnet6 then + if type(create_body["IPAM"]["Config"]) ~= "table" then + create_body["IPAM"]["Config"] = {} + end + local index = #create_body["IPAM"]["Config"] + create_body["IPAM"]["Config"][index+1] = { + Subnet = data.subnet6, + Gateway = data.gateway6 + } + end + + if next(options) ~= nil then + create_body["Options"] = create_body["Options"] or {} + for k, v in pairs(options) do + create_body["Options"][k] = v + end + end + + create_body = docker.clear_empty_tables(create_body) + docker:write_status("Network: " .. "create" .. " " .. create_body.Name .. "...") + + local res = dk.networks:create({ + body = create_body + }) + + if res and res.code == 201 then + docker:write_status("Network: " .. "create macvlan interface...") + res = dk.networks:inspect({ + name = create_body.Name + }) + + if driver == "macvlan" and + data.op_macvlan ~= 0 and + res.code == 200 and + res.body and + res.body.IPAM and + res.body.IPAM.Config and + res.body.IPAM.Config[1] and + res.body.IPAM.Config[1].Gateway and + res.body.IPAM.Config[1].Subnet then + + docker.create_macvlan_interface(data.name, + data.parent, + res.body.IPAM.Config[1].Gateway, + res.body.IPAM.Config[1].Subnet) + end + + docker:clear_status() + luci.http.redirect(luci.dispatcher.build_url("admin/docker/networks")) + else + docker:append_status("code:" .. res.code.." ".. (res.body.message and res.body.message or res.message).. "\n") + luci.http.redirect(luci.dispatcher.build_url("admin/docker/newnetwork")) + end + end end return m diff --git a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua index 5515aacc72..dc20125e29 100644 --- a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua +++ b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/overview.lua @@ -3,26 +3,29 @@ LuCI - Lua Configuration Interface Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman> ]]-- -require "luci.util" local docker = require "luci.model.docker" -local uci = require "luci.model.uci" + +local m, s, o function byte_format(byte) - local suff = {"B", "KB", "MB", "GB", "TB"} - for i=1, 5 do - if byte > 1024 and i < 5 then - byte = byte / 1024 - else - return string.format("%.2f %s", byte, suff[i]) - end - end + local suff = {"B", "KB", "MB", "GB", "TB"} + for i=1, 5 do + if byte > 1024 and i < 5 then + byte = byte / 1024 + else + return string.format("%.2f %s", byte, suff[i]) + end + end end -local map_dockerman = Map("dockerman", translate("Docker"), translate("DockerMan is a Simple Docker manager client for LuCI, If you have any issue please visit:") .. " ".. [[<a href="https://github.com/lisaac/luci-app-dockerman" target="_blank">]] ..translate("Github") .. [[</a>]]) +m = Map("dockerd", translate("Docker"), + translate("DockerMan is a Simple Docker manager client for LuCI, If you have any issue please visit:") .. + " " .. + [[<a href="https://github.com/lisaac/luci-app-dockerman" target="_blank">]] .. + translate("Github") .. + [[</a>]]) + local docker_info_table = {} --- docker_info_table['0OperatingSystem'] = {_key=translate("Operating System"),_value='-'} --- docker_info_table['1Architecture'] = {_key=translate("Architecture"),_value='-'} --- docker_info_table['2KernelVersion'] = {_key=translate("Kernel Version"),_value='-'} docker_info_table['3ServerVersion'] = {_key=translate("Docker Version"),_value='-'} docker_info_table['4ApiVersion'] = {_key=translate("Api Version"),_value='-'} docker_info_table['5NCPU'] = {_key=translate("CPUs"),_value='-'} @@ -31,124 +34,122 @@ docker_info_table['7DockerRootDir'] = {_key=translate("Docker Root Dir"),_value= docker_info_table['8IndexServerAddress'] = {_key=translate("Index Server Address"),_value='-'} docker_info_table['9RegistryMirrors'] = {_key=translate("Registry Mirrors"),_value='-'} -local s = map_dockerman:section(Table, docker_info_table) +s = m:section(Table, docker_info_table) s:option(DummyValue, "_key", translate("Info")) s:option(DummyValue, "_value") -s = map_dockerman:section(SimpleSection) + +s = m:section(SimpleSection) +s.template = "dockerman/overview" + s.containers_running = '-' s.images_used = '-' s.containers_total = '-' s.images_total = '-' s.networks_total = '-' s.volumes_total = '-' -local containers_list --- local socket = luci.model.uci.cursor():get("dockerman", "local", "socket_path") -if (require "luci.model.docker").new():_ping().code == 200 then - local dk = docker.new() - containers_list = dk.containers:list({query = {all=true}}).body - local images_list = dk.images:list().body - local vol = dk.volumes:list() - local volumes_list = vol and vol.body and vol.body.Volumes or {} - local networks_list = dk.networks:list().body or {} - local docker_info = dk:info() - -- docker_info_table['0OperatingSystem']._value = docker_info.body.OperatingSystem - -- docker_info_table['1Architecture']._value = docker_info.body.Architecture - -- docker_info_table['2KernelVersion']._value = docker_info.body.KernelVersion - docker_info_table['3ServerVersion']._value = docker_info.body.ServerVersion - docker_info_table['4ApiVersion']._value = docker_info.headers["Api-Version"] - docker_info_table['5NCPU']._value = tostring(docker_info.body.NCPU) - docker_info_table['6MemTotal']._value = byte_format(docker_info.body.MemTotal) - if docker_info.body.DockerRootDir then - local statvfs = nixio.fs.statvfs(docker_info.body.DockerRootDir) - local size = statvfs and (statvfs.bavail * statvfs.bsize) or 0 - docker_info_table['7DockerRootDir']._value = docker_info.body.DockerRootDir .. " (" .. tostring(byte_format(size)) .. " " .. translate("Available") .. ")" - end - docker_info_table['8IndexServerAddress']._value = docker_info.body.IndexServerAddress - for i, v in ipairs(docker_info.body.RegistryConfig.Mirrors) do - docker_info_table['9RegistryMirrors']._value = docker_info_table['9RegistryMirrors']._value == "-" and v or (docker_info_table['9RegistryMirrors']._value .. ", " .. v) - end - - s.images_used = 0 - for i, v in ipairs(images_list) do - for ci,cv in ipairs(containers_list) do - if v.Id == cv.ImageID then - s.images_used = s.images_used + 1 - break - end - end - end - s.containers_running = tostring(docker_info.body.ContainersRunning) - s.images_used = tostring(s.images_used) - s.containers_total = tostring(docker_info.body.Containers) - s.images_total = tostring(#images_list) - s.networks_total = tostring(#networks_list) - s.volumes_total = tostring(#volumes_list) -end -s.template = "dockerman/overview" -local section_dockerman = map_dockerman:section(NamedSection, "local", "section", translate("Setting")) -section_dockerman:tab("daemon", translate("Docker Daemon")) -section_dockerman:tab("ac", translate("Access Control")) -section_dockerman:tab("dockerman", translate("DockerMan")) +if docker.new():_ping().code == 200 then + local dk = docker.new() + local containers_list = dk.containers:list({query = {all=true}}).body + local images_list = dk.images:list().body + local vol = dk.volumes:list() + local volumes_list = vol and vol.body and vol.body.Volumes or {} + local networks_list = dk.networks:list().body or {} + local docker_info = dk:info() -local socket_path = section_dockerman:taboption("dockerman", Value, "socket_path", translate("Docker Socket Path")) -socket_path.default = "/var/run/docker.sock" -socket_path.placeholder = "/var/run/docker.sock" -socket_path.rmempty = false + docker_info_table['3ServerVersion']._value = docker_info.body.ServerVersion + docker_info_table['4ApiVersion']._value = docker_info.headers["Api-Version"] + docker_info_table['5NCPU']._value = tostring(docker_info.body.NCPU) + docker_info_table['6MemTotal']._value = byte_format(docker_info.body.MemTotal) + if docker_info.body.DockerRootDir then + local statvfs = nixio.fs.statvfs(docker_info.body.DockerRootDir) + local size = statvfs and (statvfs.bavail * statvfs.bsize) or 0 + docker_info_table['7DockerRootDir']._value = docker_info.body.DockerRootDir .. " (" .. tostring(byte_format(size)) .. " " .. translate("Available") .. ")" + end -local remote_endpoint = section_dockerman:taboption("dockerman", Flag, "remote_endpoint", translate("Remote Endpoint"), translate("Dockerman connect to remote endpoint")) -remote_endpoint.rmempty = false -remote_endpoint.enabled = "true" -remote_endpoint.disabled = "false" + docker_info_table['8IndexServerAddress']._value = docker_info.body.IndexServerAddress + for i, v in ipairs(docker_info.body.RegistryConfig.Mirrors) do + docker_info_table['9RegistryMirrors']._value = docker_info_table['9RegistryMirrors']._value == "-" and v or (docker_info_table['9RegistryMirrors']._value .. ", " .. v) + end -local remote_host = section_dockerman:taboption("dockerman", Value, "remote_host", translate("Remote Host")) -remote_host.placeholder = "10.1.1.2" --- remote_host:depends("remote_endpoint", "true") + s.images_used = 0 + for i, v in ipairs(images_list) do + for ci,cv in ipairs(containers_list) do + if v.Id == cv.ImageID then + s.images_used = s.images_used + 1 + break + end + end + end + + s.containers_running = tostring(docker_info.body.ContainersRunning) + s.images_used = tostring(s.images_used) + s.containers_total = tostring(docker_info.body.Containers) + s.images_total = tostring(#images_list) + s.networks_total = tostring(#networks_list) + s.volumes_total = tostring(#volumes_list) +end -local remote_port = section_dockerman:taboption("dockerman", Value, "remote_port", translate("Remote Port")) -remote_port.placeholder = "2375" -remote_port.default = "2375" --- remote_port:depends("remote_endpoint", "true") +s = m:section(NamedSection, "globals", "section", translate("Setting")) --- local status_path = section_dockerman:taboption("dockerman", Value, "status_path", translate("Action Status Tempfile Path"), translate("Where you want to save the docker status file")) --- local debug = section_dockerman:taboption("dockerman", Flag, "debug", translate("Enable Debug"), translate("For debug, It shows all docker API actions of luci-app-dockerman in Debug Tempfile Path")) --- debug.enabled="true" --- debug.disabled="false" --- local debug_path = section_dockerman:taboption("dockerman", Value, "debug_path", translate("Debug Tempfile Path"), translate("Where you want to save the debug tempfile")) +o = s:option(Flag, "remote_endpoint", + translate("Remote Endpoint"), + translate("Connect to remote endpoint")) +o.rmempty = false + +o = s:option(Value, "socket_path", + translate("Docker Socket Path")) +o.default = "unix://var/run/docker.sock" +o.placeholder = "unix://var/run/docker.sock" +o:depends("remote_endpoint", 1) + +o = s:option(Value, "remote_host", + translate("Remote Host")) +o.placeholder = "10.1.1.2" +o:depends("remote_endpoint", 1) + +o = s:option(Value, "remote_port", + translate("Remote Port")) +o.placeholder = "2375" +o.default = "2375" +o:depends("remote_endpoint", 1) if nixio.fs.access("/usr/bin/dockerd") then - local allowed_interface = section_dockerman:taboption("ac", DynamicList, "ac_allowed_interface", translate("Allowed access interfaces"), translate("Which interface(s) can access containers under the bridge network, fill-in Interface Name")) - local interfaces = luci.sys and luci.sys.net and luci.sys.net.devices() or {} - for i, v in ipairs(interfaces) do - allowed_interface:value(v, v) - end - local allowed_container = section_dockerman:taboption("ac", DynamicList, "ac_allowed_container", translate("Containers allowed to be accessed"), translate("Which container(s) under bridge network can be accessed, even from interfaces that are not allowed, fill-in Container Id or Name")) - -- allowed_container.placeholder = "container name_or_id" - if containers_list then - for i, v in ipairs(containers_list) do - if v.State == "running" and v.NetworkSettings and v.NetworkSettings.Networks and v.NetworkSettings.Networks.bridge and v.NetworkSettings.Networks.bridge.IPAddress then - allowed_container:value(v.Id:sub(1,12), v.Names[1]:sub(2) .. " | " .. v.NetworkSettings.Networks.bridge.IPAddress) - end - end - end - - local dockerd_enable = section_dockerman:taboption("daemon", Flag, "daemon_ea", translate("Enable")) - dockerd_enable.enabled = "true" - dockerd_enable.rmempty = true - local data_root = section_dockerman:taboption("daemon", Value, "daemon_data_root", translate("Docker Root Dir")) - data_root.placeholder = "/opt/docker/" - local registry_mirrors = section_dockerman:taboption("daemon", DynamicList, "daemon_registry_mirrors", translate("Registry Mirrors")) - registry_mirrors:value("https://hub-mirror.c.163.com", "https://hub-mirror.c.163.com") - - local log_level = section_dockerman:taboption("daemon", ListValue, "daemon_log_level", translate("Log Level"), translate('Set the logging level')) - log_level:value("debug", "debug") - log_level:value("info", "info") - log_level:value("warn", "warn") - log_level:value("error", "error") - log_level:value("fatal", "fatal") - local hosts = section_dockerman:taboption("daemon", DynamicList, "daemon_hosts", translate("Server Host"), translate('Daemon unix socket (unix:///var/run/docker.sock) or TCP Remote Hosts (tcp://0.0.0.0:2375), default: unix:///var/run/docker.sock')) - hosts:value("unix:///var/run/docker.sock", "unix:///var/run/docker.sock") - hosts:value("tcp://0.0.0.0:2375", "tcp://0.0.0.0:2375") - hosts.rmempty = true + o = s:option(Value, "data_root", + translate("Docker Root Dir")) + o.placeholder = "/opt/docker/" + o:depends("remote_endpoint", 0) + + o = s:option(Value, "bip", + translate("Default bridge"), + translate("Configure the default bridge network")) + o.placeholder = "172.17.0.1/16" + o.default = "172.17.0.1/16" + o.datatype = "ipaddr" + o:depends("remote_endpoint", 0) + + o = s:option(DynamicList, "registry_mirrors", + translate("Registry Mirrors")) + o:value("https://hub-mirror.c.163.com", "https://hub-mirror.c.163.com") + o:depends("remote_endpoint", 0) + + o = s:option(ListValue, "log_level", + translate("Log Level"), + translate('Set the logging level')) + o:value("debug", "debug") + o:value("info", "info") + o:value("warn", "warn") + o:value("error", "error") + o:value("fatal", "fatal") + o:depends("remote_endpoint", 0) + + o = s:option(DynamicList, "hosts", + translate("Client connection"), + translate('Specifies where the Docker daemon will listen for client connections')) + o:value("unix://var/run/docker.sock", "unix://var/run/docker.sock") + o:value("tcp://0.0.0.0:2375", "tcp://0.0.0.0:2375") + o.rmempty = true + o:depends("remote_endpoint", 0) end -return map_dockerman + +return m diff --git a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua index 1685027203..8dae4a020d 100644 --- a/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua +++ b/applications/luci-app-dockerman/luasrc/model/cbi/dockerman/volumes.lua @@ -3,114 +3,140 @@ LuCI - Lua Configuration Interface Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman> ]]-- -require "luci.util" -local uci = luci.model.uci.cursor() local docker = require "luci.model.docker" local dk = docker.new() -local containers, volumes -local res = dk.volumes:list() -if res.code <300 then volumes = res.body.Volumes else return end -res = dk.containers:list({query = {all=true}}) -if res.code <300 then containers = res.body else return end +local m, s, o + +local res, containers, volumes function get_volumes() - local data = {} - for i, v in ipairs(volumes) do - -- local index = v.CreatedAt .. v.Name - local index = v.Name - data[index]={} - data[index]["_selected"] = 0 - data[index]["_nameraw"] = v.Name - data[index]["_name"] = v.Name:sub(1,12) - for ci,cv in ipairs(containers) do - if cv.Mounts and type(cv.Mounts) ~= "table" then break end - for vi, vv in ipairs(cv.Mounts) do - if v.Name == vv.Name then - data[index]["_containers"] = (data[index]["_containers"] and (data[index]["_containers"] .. " | ") or "").. - '<a href='..luci.dispatcher.build_url("admin/docker/container/"..cv.Id)..' class="dockerman_link" title="'..translate("Container detail")..'">'.. cv.Names[1]:sub(2)..'</a>' - end - end - end - data[index]["_driver"] = v.Driver - data[index]["_mountpoint"] = nil - for v1 in v.Mountpoint:gmatch('[^/]+') do - if v1 == index then - data[index]["_mountpoint"] = data[index]["_mountpoint"] .."/" .. v1:sub(1,12) .. "..." - else - data[index]["_mountpoint"] = (data[index]["_mountpoint"] and data[index]["_mountpoint"] or "").."/".. v1 - end - end - data[index]["_created"] = v.CreatedAt - end - return data + local data = {} + for i, v in ipairs(volumes) do + local index = v.Name + data[index]={} + data[index]["_selected"] = 0 + data[index]["_nameraw"] = v.Name + data[index]["_name"] = v.Name:sub(1,12) + + for ci,cv in ipairs(containers) do + if cv.Mounts and type(cv.Mounts) ~= "table" then + break + end + for vi, vv in ipairs(cv.Mounts) do + if v.Name == vv.Name then + data[index]["_containers"] = (data[index]["_containers"] and (data[index]["_containers"] .. " | ") or "").. + '<a href='..luci.dispatcher.build_url("admin/docker/container/"..cv.Id)..' class="dockerman_link" title="'..translate("Container detail")..'">'.. cv.Names[1]:sub(2)..'</a>' + end + end + end + data[index]["_driver"] = v.Driver + data[index]["_mountpoint"] = nil + + for v1 in v.Mountpoint:gmatch('[^/]+') do + if v1 == index then + data[index]["_mountpoint"] = data[index]["_mountpoint"] .."/" .. v1:sub(1,12) .. "..." + else + data[index]["_mountpoint"] = (data[index]["_mountpoint"] and data[index]["_mountpoint"] or "").."/".. v1 + end + end + data[index]["_created"] = v.CreatedAt + end + + return data +end + +res = dk.volumes:list() +if res.code <300 then + volumes = res.body.Volumes +else + return +end + +res = dk.containers:list({ + query = { + all=true + } +}) +if res.code <300 then + containers = res.body +else + return end local volume_list = get_volumes() --- m = Map("docker", translate("Docker")) m = SimpleForm("docker", translate("Docker")) m.submit=false m.reset=false +s = m:section(Table, volume_list, translate("Volumes")) + +o = s:option(Flag, "_selected","") +o.disabled = 0 +o.enabled = 1 +o.default = 0 +o.write = function(self, section, value) + volume_list[section]._selected = value +end + +o = s:option(DummyValue, "_name", translate("Name")) + +o = s:option(DummyValue, "_driver", translate("Driver")) -volume_table = m:section(Table, volume_list, translate("Volumes")) +o = s:option(DummyValue, "_containers", translate("Containers")) +o.rawhtml = true -volume_selecter = volume_table:option(Flag, "_selected","") -volume_selecter.disabled = 0 -volume_selecter.enabled = 1 -volume_selecter.default = 0 +o = s:option(DummyValue, "_mountpoint", translate("Mount Point")) -volume_id = volume_table:option(DummyValue, "_name", translate("Name")) -volume_table:option(DummyValue, "_driver", translate("Driver")) -volume_table:option(DummyValue, "_containers", translate("Containers")).rawhtml = true -volume_table:option(DummyValue, "_mountpoint", translate("Mount Point")) -volume_table:option(DummyValue, "_created", translate("Created")) -volume_selecter.write = function(self, section, value) - volume_list[section]._selected = value +o = s:option(DummyValue, "_created", translate("Created")) + +s = m:section(SimpleSection) +s.template = "dockerman/apply_widget" +s.err=docker:read_status() +s.err=s.err and s.err:gsub("\n","<br>"):gsub(" "," ") +if s.err then + docker:clear_status() end -docker_status = m:section(SimpleSection) -docker_status.template = "dockerman/apply_widget" -docker_status.err=docker:read_status() -docker_status.err=docker_status.err and docker_status.err:gsub("\n","<br>"):gsub(" "," ") -if docker_status.err then docker:clear_status() end - -action = m:section(Table,{{}}) -action.notitle=true -action.rowcolors=false -action.template="cbi/nullsection" -btnremove = action:option(Button, "remove") -btnremove.inputtitle= translate("Remove") -btnremove.template = "dockerman/cbi/inlinebutton" -btnremove.inputstyle = "remove" -btnremove.forcewrite = true -btnremove.write = function(self, section) - local volume_selected = {} - -- 遍历table中sectionid - local volume_table_sids = volume_table:cfgsections() - for _, volume_table_sid in ipairs(volume_table_sids) do - -- 得到选中项的名字 - if volume_list[volume_table_sid]._selected == 1 then - -- volume_selected[#volume_selected+1] = volume_id:cfgvalue(volume_table_sid) - volume_selected[#volume_selected+1] = volume_table_sid - end - end - if next(volume_selected) ~= nil then - local success = true - docker:clear_status() - for _,vol in ipairs(volume_selected) do - docker:append_status("Volumes: " .. "remove" .. " " .. vol .. "...") - local msg = dk.volumes["remove"](dk, {id = vol}) - if msg.code ~= 204 then - docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n") - success = false - else - docker:append_status("done\n") - end - end - if success then docker:clear_status() end - luci.http.redirect(luci.dispatcher.build_url("admin/docker/volumes")) - end +s = m:section(Table,{{}}) +s.notitle=true +s.rowcolors=false +s.template="cbi/nullsection" + +o = s:option(Button, "remove") +o.inputtitle= translate("Remove") +o.template = "dockerman/cbi/inlinebutton" +o.inputstyle = "remove" +o.forcewrite = true +o.write = function(self, section) + local volume_selected = {} + + for k in pairs(volume_list) do + if volume_list[k]._selected == 1 then + volume_selected[#volume_selected+1] = k + end + end + + if next(volume_selected) ~= nil then + local success = true + docker:clear_status() + for _,vol in ipairs(volume_selected) do + docker:append_status("Volumes: " .. "remove" .. " " .. vol .. "...") + local msg = dk.volumes["remove"](dk, {id = vol}) + if msg.code ~= 204 then + docker:append_status("code:" .. msg.code.." ".. (msg.body.message and msg.body.message or msg.message).. "\n") + success = false + else + docker:append_status("done\n") + end + end + + if success then + docker:clear_status() + end + luci.http.redirect(luci.dispatcher.build_url("admin/docker/volumes")) + end end + return m diff --git a/applications/luci-app-dockerman/luasrc/model/docker.lua b/applications/luci-app-dockerman/luasrc/model/docker.lua index e62454a8fc..a0c74c0e41 100644 --- a/applications/luci-app-dockerman/luasrc/model/docker.lua +++ b/applications/luci-app-dockerman/luasrc/model/docker.lua @@ -3,276 +3,347 @@ LuCI - Lua Configuration Interface Copyright 2019 lisaac <https://github.com/lisaac/luci-app-dockerman> ]]-- -require "luci.util" local docker = require "luci.docker" +local fs = require "nixio.fs" local uci = (require "luci.model.uci").cursor() local _docker = {} +_docker.options = {} --pull image and return iamge id local update_image = function(self, image_name) - local json_stringify = luci.jsonc and luci.jsonc.stringify - _docker:append_status("Images: " .. "pulling" .. " " .. image_name .. "...\n") - local res = self.images:create({query = {fromImage=image_name}}, _docker.pull_image_show_status_cb) - if res and res.code == 200 and (#res.body > 0 and not res.body[#res.body].error and res.body[#res.body].status and (res.body[#res.body].status == "Status: Downloaded newer image for ".. image_name)) then - _docker:append_status("done\n") - else - res.body.message = res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message) - end - new_image_id = self.images:inspect({name = image_name}).body.Id - return new_image_id, res + local json_stringify = luci.jsonc and luci.jsonc.stringify + _docker:append_status("Images: " .. "pulling" .. " " .. image_name .. "...\n") + local res = self.images:create({query = {fromImage=image_name}}, _docker.pull_image_show_status_cb) + + if res and res.code == 200 and (#res.body > 0 and not res.body[#res.body].error and res.body[#res.body].status and (res.body[#res.body].status == "Status: Downloaded newer image for ".. image_name)) then + _docker:append_status("done\n") + else + res.body.message = res.body[#res.body] and res.body[#res.body].error or (res.body.message or res.message) + end + + new_image_id = self.images:inspect({name = image_name}).body.Id + return new_image_id, res end local table_equal = function(t1, t2) - if not t1 then return true end - if not t2 then return false end - if #t1 ~= #t2 then return false end - for i, v in ipairs(t1) do - if t1[i] ~= t2[i] then return false end - end - return true + if not t1 then + return true + end + + if not t2 then + return false + end + + if #t1 ~= #t2 then + return false + end + + for i, v in ipairs(t1) do + if t1[i] ~= t2[i] then + return false + end + end + + return true end local table_subtract = function(t1, t2) - if not t1 or next(t1) == nil then return nil end - if not t2 or next(t2) == nil then return t1 end - local res = {} - for _, v1 in ipairs(t1) do - local found = false - for _, v2 in ipairs(t2) do - if v1 == v2 then - found= true - break - end - end - if not found then - table.insert(res, v1) - end - end - return next(res) == nil and nil or res + if not t1 or next(t1) == nil then + return nil + end + + if not t2 or next(t2) == nil then + return t1 + end + + local res = {} + for _, v1 in ipairs(t1) do + local found = false + for _, v2 in ipairs(t2) do + if v1 == v2 then + found= true + break + end + end + if not found then + table.insert(res, v1) + end + end + + return next(res) == nil and nil or res end local map_subtract = function(t1, t2) - if not t1 or next(t1) == nil then return nil end - if not t2 or next(t2) == nil then return t1 end - local res = {} - for k1, v1 in pairs(t1) do - local found = false - for k2, v2 in ipairs(t2) do - if k1 == k2 and luci.util.serialize_data(v1) == luci.util.serialize_data(v2) then - found= true - break - end - end - if not found then - res[k1] = v1 - -- if v1 and type(v1) == "table" then - -- if next(v1) == nil then - -- res[k1] = { k = 'v' } - -- else - -- res[k1] = v1 - -- end - -- end - end - end - - return next(res) ~= nil and res or nil + if not t1 or next(t1) == nil then + return nil + end + + if not t2 or next(t2) == nil then + return t1 + end + + local res = {} + for k1, v1 in pairs(t1) do + local found = false + for k2, v2 in ipairs(t2) do + if k1 == k2 and luci.util.serialize_data(v1) == luci.util.serialize_data(v2) then + found= true + break + end + end + + if not found then + res[k1] = v1 + end + end + + return next(res) ~= nil and res or nil end _docker.clear_empty_tables = function ( t ) - local k, v - if next(t) == nil then - t = nil - else - for k, v in pairs(t) do - if type(v) == 'table' then - t[k] = _docker.clear_empty_tables(v) - end - end - end - return t + local k, v + + if next(t) == nil then + t = nil + else + for k, v in pairs(t) do + if type(v) == 'table' then + t[k] = _docker.clear_empty_tables(v) + end + end + end + + return t end --- return create_body, extra_network local get_config = function(container_config, image_config) - local config = container_config.Config - local old_host_config = container_config.HostConfig - local old_network_setting = container_config.NetworkSettings.Networks or {} - if config.WorkingDir == image_config.WorkingDir then config.WorkingDir = "" end - if config.User == image_config.User then config.User = "" end - if table_equal(config.Cmd, image_config.Cmd) then config.Cmd = nil end - if table_equal(config.Entrypoint, image_config.Entrypoint) then config.Entrypoint = nil end - if table_equal(config.ExposedPorts, image_config.ExposedPorts) then config.ExposedPorts = nil end - config.Env = table_subtract(config.Env, image_config.Env) - config.Labels = table_subtract(config.Labels, image_config.Labels) - config.Volumes = map_subtract(config.Volumes, image_config.Volumes) - -- subtract ports exposed in image from container - if old_host_config.PortBindings and next(old_host_config.PortBindings) ~= nil then - config.ExposedPorts = {} - for p, v in pairs(old_host_config.PortBindings) do - config.ExposedPorts[p] = { HostPort=v[1] and v[1].HostPort } - end - end - - -- handle network config, we need only one network, extras need to network connect action - local network_setting = {} - local multi_network = false - local extra_network = {} - for k, v in pairs(old_network_setting) do - if multi_network then - extra_network[k] = v - else - network_setting[k] = v - end - multi_network = true - end - - -- handle hostconfig - local host_config = old_host_config - -- if host_config.PortBindings and next(host_config.PortBindings) == nil then host_config.PortBindings = nil end - -- host_config.LogConfig = nil - host_config.Mounts = {} - -- for volumes - for i, v in ipairs(container_config.Mounts) do - if v.Type == "volume" then - table.insert(host_config.Mounts, { - Type = v.Type, - Target = v.Destination, - Source = v.Source:match("([^/]+)\/_data"), - BindOptions = (v.Type == "bind") and {Propagation = v.Propagation} or nil, - ReadOnly = not v.RW - }) - end - end - - - -- merge configs - local create_body = config - create_body["HostConfig"] = host_config - create_body["NetworkingConfig"] = {EndpointsConfig = network_setting} - create_body = _docker.clear_empty_tables(create_body) or {} - extra_network = _docker.clear_empty_tables(extra_network) or {} - return create_body, extra_network + local config = container_config.Config + local old_host_config = container_config.HostConfig + local old_network_setting = container_config.NetworkSettings.Networks or {} + + if config.WorkingDir == image_config.WorkingDir then + config.WorkingDir = "" + end + + if config.User == image_config.User then + config.User = "" + end + + if table_equal(config.Cmd, image_config.Cmd) then + config.Cmd = nil + end + + if table_equal(config.Entrypoint, image_config.Entrypoint) then + config.Entrypoint = nil + end + + if table_equal(config.ExposedPorts, image_config.ExposedPorts) then + config.ExposedPorts = nil + end + + config.Env = table_subtract(config.Env, image_config.Env) + config.Labels = table_subtract(config.Labels, image_config.Labels) + config.Volumes = map_subtract(config.Volumes, image_config.Volumes) + + if old_host_config.PortBindings and next(old_host_config.PortBindings) ~= nil then + config.ExposedPorts = {} + for p, v in pairs(old_host_config.PortBindings) do + config.ExposedPorts[p] = { HostPort=v[1] and v[1].HostPort } + end + end + + local network_setting = {} + local multi_network = false + local extra_network = {} + + for k, v in pairs(old_network_setting) do + if multi_network then + extra_network[k] = v + else + network_setting[k] = v + end + multi_network = true + end + + local host_config = old_host_config + host_config.Mounts = {} + for i, v in ipairs(container_config.Mounts) do + if v.Type == "volume" then + table.insert(host_config.Mounts, { + Type = v.Type, + Target = v.Destination, + Source = v.Source:match("([^/]+)\/_data"), + BindOptions = (v.Type == "bind") and {Propagation = v.Propagation} or nil, + ReadOnly = not v.RW + }) + end + end + + local create_body = config + create_body["HostConfig"] = host_config + create_body["NetworkingConfig"] = {EndpointsConfig = network_setting} + create_body = _docker.clear_empty_tables(create_body) or {} + extra_network = _docker.clear_empty_tables(extra_network) or {} + + return create_body, extra_network end local upgrade = function(self, request) - _docker:clear_status() - -- get image name, image id, container name, configuration information - local container_info = self.containers:inspect({id = request.id}) - if container_info.code > 300 and type(container_info.body) == "table" then - return container_info - end - local image_name = container_info.body.Config.Image - if not image_name:match(".-:.+") then image_name = image_name .. ":latest" end - local old_image_id = container_info.body.Image - local container_name = container_info.body.Name:sub(2) - - local image_id, res = update_image(self, image_name) - if res and res.code ~= 200 then return res end - if image_id == old_image_id then - return {code = 305, body = {message = "Already up to date"}} - end - - _docker:append_status("Container: " .. "Stop" .. " " .. container_name .. "...") - res = self.containers:stop({name = container_name}) - if res and res.code < 305 then - _docker:append_status("done\n") - else - return res - end - - _docker:append_status("Container: rename" .. " " .. container_name .. " to ".. container_name .. "_old ...") - res = self.containers:rename({name = container_name, query = { name = container_name .. "_old" }}) - if res and res.code < 300 then - _docker:append_status("done\n") - else - return res - end - - -- handle config - local image_config = self.images:inspect({id = old_image_id}).body.Config - local create_body, extra_network = get_config(container_info.body, image_config) - - -- create new container - _docker:append_status("Container: Create" .. " " .. container_name .. "...") - create_body = _docker.clear_empty_tables(create_body) - res = self.containers:create({name = container_name, body = create_body}) - if res and res.code > 300 then return res end - _docker:append_status("done\n") - - -- extra networks need to network connect action - for k, v in pairs(extra_network) do - _docker:append_status("Networks: Connect" .. " " .. container_name .. "...") - res = self.networks:connect({id = k, body = {Container = container_name, EndpointConfig = v}}) - if res.code > 300 then return res end - - _docker:append_status("done\n") - end - _docker:clear_status() - return res + _docker:clear_status() + + local container_info = self.containers:inspect({id = request.id}) + + if container_info.code > 300 and type(container_info.body) == "table" then + return container_info + end + + local image_name = container_info.body.Config.Image + if not image_name:match(".-:.+") then + image_name = image_name .. ":latest" + end + + local old_image_id = container_info.body.Image + local container_name = container_info.body.Name:sub(2) + + local image_id, res = update_image(self, image_name) + if res and res.code ~= 200 then + return res + end + + if image_id == old_image_id then + return {code = 305, body = {message = "Already up to date"}} + end + + _docker:append_status("Container: " .. "Stop" .. " " .. container_name .. "...") + res = self.containers:stop({name = container_name}) + if res and res.code < 305 then + _docker:append_status("done\n") + else + return res + end + + _docker:append_status("Container: rename" .. " " .. container_name .. " to ".. container_name .. "_old ...") + res = self.containers:rename({name = container_name, query = { name = container_name .. "_old" }}) + if res and res.code < 300 then + _docker:append_status("done\n") + else + return res + end + + local image_config = self.images:inspect({id = old_image_id}).body.Config + local create_body, extra_network = get_config(container_info.body, image_config) + + -- create new container + _docker:append_status("Container: Create" .. " " .. container_name .. "...") + create_body = _docker.clear_empty_tables(create_body) + res = self.containers:create({name = container_name, body = create_body}) + if res and res.code > 300 then + return res + end + _docker:append_status("done\n") + + -- extra networks need to network connect action + for k, v in pairs(extra_network) do + _docker:append_status("Networks: Connect" .. " " .. container_name .. "...") + res = self.networks:connect({id = k, body = {Container = container_name, EndpointConfig = v}}) + if res.code > 300 then + return res + end + _docker:append_status("done\n") + end + + _docker:clear_status() + return res end local duplicate_config = function (self, request) - local container_info = self.containers:inspect({id = request.id}) - if container_info.code > 300 and type(container_info.body) == "table" then return nil end - local old_image_id = container_info.body.Image - local image_config = self.images:inspect({id = old_image_id}).body.Config - return get_config(container_info.body, image_config) + local container_info = self.containers:inspect({id = request.id}) + if container_info.code > 300 and type(container_info.body) == "table" then + return nil + end + + local old_image_id = container_info.body.Image + local image_config = self.images:inspect({id = old_image_id}).body.Config + + return get_config(container_info.body, image_config) end -_docker.new = function(option) - local option = option or {} - local remote = uci:get("dockerman", "local", "remote_endpoint") - options = { - host = (remote == "true") and (option.host or uci:get("dockerman", "local", "remote_host")) or nil, - port = (remote == "true") and (option.port or uci:get("dockerman", "local", "remote_port")) or nil, - debug = option.debug or uci:get("dockerman", "local", "debug") == 'true' and true or false, - debug_path = option.debug_path or uci:get("dockerman", "local", "debug_path") - } - options.socket_path = (remote ~= "true" or not options.host or not options.port) and (option.socket_path or uci:get("dockerman", "local", "socket_path") or "/var/run/docker.sock") or nil - local _new = docker.new(options) - _new.options.status_path = uci:get("dockerman", "local", "status_path") - _new.containers_upgrade = upgrade - _new.containers_duplicate_config = duplicate_config - return _new +_docker.new = function() + local host = nil + local port = nil + local socket_path = nil + local debug_path = nil + + local remote = uci:get_bool("dockerd", "globals", "remote_endpoint") + if remote then + host = uci:get("dockerd", "globals", "remote_host") or nil + port = uci:get("dockerd", "globals", "remote_port") or nil + else + socket_path = uci:get("dockerd", "globals", "socket_path") or "/var/run/docker.sock" + end + + local debug = uci:get_bool("dockerd", "globals", "debug") + if debug then + debug_path = uci:get("dockerd", "globals", "debug_path") or "/tmp/.docker_debug" + end + + local status_path = uci:get("dockerd", "globals", "status_path") or "/tmp/.docker_status" + + _docker.options = { + host = host, + port = port, + socket_path = socket_path, + debug = debug, + debug_path = debug_path, + status_path = status_path + } + + local _new = docker.new(_docker.options) + _new.containers_upgrade = upgrade + _new.containers_duplicate_config = duplicate_config + + return _new end -_docker.options={} -_docker.options.status_path = uci:get("dockerman", "local", "status_path") _docker.append_status=function(self,val) - if not val then return end - local file_docker_action_status=io.open(self.options.status_path, "a+") - file_docker_action_status:write(val) - file_docker_action_status:close() + if not val then + return + end + local file_docker_action_status=io.open(self.options.status_path, "a+") + file_docker_action_status:write(val) + file_docker_action_status:close() end _docker.write_status=function(self,val) - if not val then return end - local file_docker_action_status=io.open(self.options.status_path, "w+") - file_docker_action_status:write(val) - file_docker_action_status:close() + if not val then + return + end + local file_docker_action_status=io.open(self.options.status_path, "w+") + file_docker_action_status:write(val) + file_docker_action_status:close() end _docker.read_status=function(self) - return nixio.fs.readfile(self.options.status_path) + return fs.readfile(self.options.status_path) end _docker.clear_status=function(self) - nixio.fs.remove(self.options.status_path) + fs.remove(self.options.status_path) end local status_cb = function(res, source, handler) - res.body = res.body or {} - while true do - local chunk = source() - if chunk then - --standard output to res.body - table.insert(res.body, chunk) - handler(chunk) - else - return - end - end + res.body = res.body or {} + while true do + local chunk = source() + if chunk then + --standard output to res.body + table.insert(res.body, chunk) + handler(chunk) + else + return + end + end end --{"status":"Pulling from library\/debian","id":"latest"} @@ -284,114 +355,128 @@ end --{"status":"Digest: sha256:a63d0b2ecbd723da612abf0a8bdb594ee78f18f691d7dc652ac305a490c9b71a"} --{"status":"Status: Downloaded newer image for debian:latest"} _docker.pull_image_show_status_cb = function(res, source) - return status_cb(res, source, function(chunk) - local json_parse = luci.jsonc.parse - local step = json_parse(chunk) - if type(step) == "table" then - local buf = _docker:read_status() - local num = 0 - local str = '\t' .. (step.id and (step.id .. ": ") or "") .. (step.status and step.status or "") .. (step.progress and (" " .. step.progress) or "").."\n" - if step.id then buf, num = buf:gsub("\t"..step.id .. ": .-\n", str) end - if num == 0 then - buf = buf .. str - end - _docker:write_status(buf) - end - end) + return status_cb(res, source, function(chunk) + local json_parse = luci.jsonc.parse + local step = json_parse(chunk) + if type(step) == "table" then + local buf = _docker:read_status() + local num = 0 + local str = '\t' .. (step.id and (step.id .. ": ") or "") .. (step.status and step.status or "") .. (step.progress and (" " .. step.progress) or "").."\n" + if step.id then + buf, num = buf:gsub("\t"..step.id .. ": .-\n", str) + end + if num == 0 then + buf = buf .. str + end + _docker:write_status(buf) + end + end) end --{"status":"Downloading from https://downloads.openwrt.org/releases/19.07.0/targets/x86/64/openwrt-19.07.0-x86-64-generic-rootfs.tar.gz"} --{"status":"Importing","progressDetail":{"current":1572391,"total":3821714},"progress":"[====================\u003e ] 1.572MB/3.822MB"} --{"status":"sha256:d5304b58e2d8cc0a2fd640c05cec1bd4d1229a604ac0dd2909f13b2b47a29285"} _docker.import_image_show_status_cb = function(res, source) - return status_cb(res, source, function(chunk) - local json_parse = luci.jsonc.parse - local step = json_parse(chunk) - if type(step) == "table" then - local buf = _docker:read_status() - local num = 0 - local str = '\t' .. (step.status and step.status or "") .. (step.progress and (" " .. step.progress) or "").."\n" - if step.status then buf, num = buf:gsub("\t"..step.status .. " .-\n", str) end - if num == 0 then - buf = buf .. str - end - _docker:write_status(buf) - end - end - ) + return status_cb(res, source, function(chunk) + local json_parse = luci.jsonc.parse + local step = json_parse(chunk) + if type(step) == "table" then + local buf = _docker:read_status() + local num = 0 + local str = '\t' .. (step.status and step.status or "") .. (step.progress and (" " .. step.progress) or "").."\n" + if step.status then + buf, num = buf:gsub("\t"..step.status .. " .-\n", str) + end + if num == 0 then + buf = buf .. str + end + _docker:write_status(buf) + end + end) end --- _docker.print_status_cb = function(res, source) --- return status_cb(res, source, function(step) --- luci.util.perror(step) --- end --- ) --- end - _docker.create_macvlan_interface = function(name, device, gateway, subnet) - if not nixio.fs.access("/etc/config/network") or not nixio.fs.access("/etc/config/firewall") then return end - if uci:get("dockerman", "local", "remote_endpoint") == "true" then return end - local ip = require "luci.ip" - local if_name = "docker_"..name - local dev_name = "macvlan_"..name - local net_mask = tostring(ip.new(subnet):mask()) - local lan_interfaces - -- add macvlan device - uci:delete("network", dev_name) - uci:set("network", dev_name, "device") - uci:set("network", dev_name, "name", dev_name) - uci:set("network", dev_name, "ifname", device) - uci:set("network", dev_name, "type", "macvlan") - uci:set("network", dev_name, "mode", "bridge") - -- add macvlan interface - uci:delete("network", if_name) - uci:set("network", if_name, "interface") - uci:set("network", if_name, "proto", "static") - uci:set("network", if_name, "ifname", dev_name) - uci:set("network", if_name, "ipaddr", gateway) - uci:set("network", if_name, "netmask", net_mask) - uci:foreach("firewall", "zone", function(s) - if s.name == "lan" then - local interfaces - if type(s.network) == "table" then - interfaces = table.concat(s.network, " ") - uci:delete("firewall", s[".name"], "network") - else - interfaces = s.network and s.network or "" - end - interfaces = interfaces .. " " .. if_name - interfaces = interfaces:gsub("%s+", " ") - uci:set("firewall", s[".name"], "network", interfaces) - end - end) - uci:commit("firewall") - uci:commit("network") - os.execute("ifup " .. if_name) + if not fs.access("/etc/config/network") or not fs.access("/etc/config/firewall") then + return + end + + if uci:get("dockerd", "globals", "remote_endpoint") == "true" then + return + end + + local ip = require "luci.ip" + local if_name = "docker_"..name + local dev_name = "macvlan_"..name + local net_mask = tostring(ip.new(subnet):mask()) + local lan_interfaces + + -- add macvlan device + uci:delete("network", dev_name) + uci:set("network", dev_name, "device") + uci:set("network", dev_name, "name", dev_name) + uci:set("network", dev_name, "ifname", device) + uci:set("network", dev_name, "type", "macvlan") + uci:set("network", dev_name, "mode", "bridge") + + -- add macvlan interface + uci:delete("network", if_name) + uci:set("network", if_name, "interface") + uci:set("network", if_name, "proto", "static") + uci:set("network", if_name, "ifname", dev_name) + uci:set("network", if_name, "ipaddr", gateway) + uci:set("network", if_name, "netmask", net_mask) + uci:foreach("firewall", "zone", function(s) + if s.name == "lan" then + local interfaces + if type(s.network) == "table" then + interfaces = table.concat(s.network, " ") + uci:delete("firewall", s[".name"], "network") + else + interfaces = s.network and s.network or "" + end + interfaces = interfaces .. " " .. if_name + interfaces = interfaces:gsub("%s+", " ") + uci:set("firewall", s[".name"], "network", interfaces) + end + end) + + uci:commit("firewall") + uci:commit("network") + + os.execute("ifup " .. if_name) end _docker.remove_macvlan_interface = function(name) - if not nixio.fs.access("/etc/config/network") or not nixio.fs.access("/etc/config/firewall") then return end - if uci:get("dockerman", "local", "remote_endpoint") == "true" then return end - local if_name = "docker_"..name - local dev_name = "macvlan_"..name - uci:foreach("firewall", "zone", function(s) - if s.name == "lan" then - local interfaces - if type(s.network) == "table" then - interfaces = table.concat(s.network, " ") - else - interfaces = s.network and s.network or "" - end - interfaces = interfaces and interfaces:gsub(if_name, "") - interfaces = interfaces and interfaces:gsub("%s+", " ") - uci:set("firewall", s[".name"], "network", interfaces) - end - end) - uci:commit("firewall") - uci:delete("network", dev_name) - uci:delete("network", if_name) - uci:commit("network") - os.execute("ip link del " .. if_name) + if not fs.access("/etc/config/network") or not fs.access("/etc/config/firewall") then + return + end + + if uci:get("dockerd", "globals", "remote_endpoint") == "true" then + return + end + + local if_name = "docker_"..name + local dev_name = "macvlan_"..name + uci:foreach("firewall", "zone", function(s) + if s.name == "lan" then + local interfaces + if type(s.network) == "table" then + interfaces = table.concat(s.network, " ") + else + interfaces = s.network and s.network or "" + end + interfaces = interfaces and interfaces:gsub(if_name, "") + interfaces = interfaces and interfaces:gsub("%s+", " ") + uci:set("firewall", s[".name"], "network", interfaces) + end + end) + + uci:delete("network", dev_name) + uci:delete("network", if_name) + uci:commit("network") + uci:commit("firewall") + + os.execute("ip link del " .. if_name) end return _docker diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/apply_widget.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/apply_widget.htm index 4deb6a88ae..189055c20b 100644 --- a/applications/luci-app-dockerman/luasrc/view/dockerman/apply_widget.htm +++ b/applications/luci-app-dockerman/luasrc/view/dockerman/apply_widget.htm @@ -1,140 +1,147 @@ <style type="text/css"> - #docker_apply_overlay { - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; - background: rgba(0, 0, 0, 0.7); - display: none; - z-index: 20000; - } - - #docker_apply_overlay .alert-message { - position: relative; - top: 10%; - width: 60%; - margin: auto; - display: flex; - flex-wrap: wrap; - min-height: 32px; - align-items: center; - } - - #docker_apply_overlay .alert-message > h4, - #docker_apply_overlay .alert-message > p, - #docker_apply_overlay .alert-message > div { - flex-basis: 100%; - } - - #docker_apply_overlay .alert-message > img { - margin-right: 1em; - flex-basis: 32px; - } - - body.apply-overlay-active { - overflow: hidden; - height: 100vh; - } - - body.apply-overlay-active #docker_apply_overlay { - display: block; - } + #docker_apply_overlay { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + background: rgba(0, 0, 0, 0.7); + display: none; + z-index: 20000; + } + + #docker_apply_overlay .alert-message { + position: relative; + top: 10%; + width: 60%; + margin: auto; + display: flex; + flex-wrap: wrap; + min-height: 32px; + align-items: center; + } + + #docker_apply_overlay .alert-message > h4, + #docker_apply_overlay .alert-message > p, + #docker_apply_overlay .alert-message > div { + flex-basis: 100%; + } + + #docker_apply_overlay .alert-message > img { + margin-right: 1em; + flex-basis: 32px; + } + + body.apply-overlay-active { + overflow: hidden; + height: 100vh; + } + + body.apply-overlay-active #docker_apply_overlay { + display: block; + } </style> + <script type="text/javascript">//<![CDATA[ - var xhr = new XHR(), - uci_apply_rollback = <%=math.max(luci.config and luci.config.apply and luci.config.apply.rollback or 30, 30)%>, - uci_apply_holdoff = <%=math.max(luci.config and luci.config.apply and luci.config.apply.holdoff or 4, 1)%>, - uci_apply_timeout = <%=math.max(luci.config and luci.config.apply and luci.config.apply.timeout or 5, 1)%>, - uci_apply_display = <%=math.max(luci.config and luci.config.apply and luci.config.apply.display or 1.5, 1)%>, - was_xhr_poll_running = false; - -function docker_status_message(type, content) { - document.getElementById('docker_apply_overlay') || document.body.insertAdjacentHTML("beforeend",'<div id="docker_apply_overlay"><div class="alert-message"></div></div>') - var overlay = document.getElementById('docker_apply_overlay') - message = overlay.querySelector('.alert-message'); - - if (message && type) { - if (!message.classList.contains(type)) { - message.classList.remove('notice'); - message.classList.remove('warning'); - message.classList.add(type); - } - - if (content) - message.innerHTML = content; - - document.body.classList.add('apply-overlay-active'); - document.body.scrollTop = document.documentElement.scrollTop = 0; - if (!was_xhr_poll_running) { - was_xhr_poll_running = XHR.running(); - XHR.halt(); - } - } - else { - document.body.classList.remove('apply-overlay-active'); - - if (was_xhr_poll_running) - XHR.run(); - } -} -var loading_msg="Loading.." -function uci_confirm_docker() { - var tt; - docker_status_message('notice'); - var call = function(r, resjson, duration) { - if (r && r.status === 200 ) { - var indicator = document.querySelector('.uci_change_indicator'); - if (indicator) indicator.style.display = 'none'; - docker_status_message('notice', '<%:Docker actions done.%>'); - document.body.classList.remove('apply-overlay-active'); - window.clearTimeout(tt); - return; - } - loading_msg = resjson?resjson.info:loading_msg - // var delay = isNaN(duration) ? 0 : Math.max(1000 - duration, 0); - var delay =1000 - window.setTimeout(function() { - xhr.get('<%=url("admin/docker/confirm")%>', null, call, uci_apply_timeout * 1000); - }, delay); - }; - - var tick = function() { - var now = Date.now(); - - docker_status_message('notice', - '<img src="<%=resource%>/icons/loading.gif" alt="" style="vertical-align:middle" /> <span style="white-space:pre-line; word-break:break-all; font-family: \'Courier New\', Courier, monospace;">' + - loading_msg + '</span>'); - - tt = window.setTimeout(tick, 200); - ts = now; - }; - tick(); - /* wait a few seconds for the settings to become effective */ - window.setTimeout(call, Math.max(uci_apply_holdoff * 1000 , 1)); - } - // document.getElementsByTagName("form")[0].addEventListener("submit", (e)=>{ - // uci_confirm_docker() - // }) - -function fnSubmitForm(el){ - if (el.id != "cbid.table.1._new") { - uci_confirm_docker() - } -} - -<% if self.err then -%> - docker_status_message('warning', '<span style="white-space:pre-line; word-break:break-all; font-family: \'Courier New\', Courier, monospace;">'+`<%=self.err%>`+'</span>'); - document.getElementById('docker_apply_overlay').addEventListener("click", (e)=>{ - docker_status_message() - }) -<%- end %> - -window.onload= function (){ -var buttons = document.querySelectorAll('input[type="submit"]'); -[].slice.call(buttons).forEach(function (el) { - el.onclick = fnSubmitForm.bind(this, el); -}); -} + var xhr = new XHR(), + uci_apply_rollback = <%=math.max(luci.config and luci.config.apply and luci.config.apply.rollback or 30, 30)%>, + uci_apply_holdoff = <%=math.max(luci.config and luci.config.apply and luci.config.apply.holdoff or 4, 1)%>, + uci_apply_timeout = <%=math.max(luci.config and luci.config.apply and luci.config.apply.timeout or 5, 1)%>, + uci_apply_display = <%=math.max(luci.config and luci.config.apply and luci.config.apply.display or 1.5, 1)%>, + was_xhr_poll_running = false; + + function docker_status_message(type, content) { + document.getElementById('docker_apply_overlay') || document.body.insertAdjacentHTML("beforeend",'<div id="docker_apply_overlay"><div class="alert-message"></div></div>') + var overlay = document.getElementById('docker_apply_overlay') + message = overlay.querySelector('.alert-message'); + + if (message && type) { + if (!message.classList.contains(type)) { + message.classList.remove('notice'); + message.classList.remove('warning'); + message.classList.add(type); + } + + if (content) + message.innerHTML = content; + + document.body.classList.add('apply-overlay-active'); + document.body.scrollTop = document.documentElement.scrollTop = 0; + if (!was_xhr_poll_running) { + was_xhr_poll_running = XHR.running(); + XHR.halt(); + } + } + else { + document.body.classList.remove('apply-overlay-active'); + if (was_xhr_poll_running) + XHR.run(); + } + } + + var loading_msg="Loading.." + function uci_confirm_docker() { + var tt; + docker_status_message('notice'); + var call = function(r, resjson, duration) { + if (r && r.status === 200 ) { + var indicator = document.querySelector('.uci_change_indicator'); + if (indicator) + indicator.style.display = 'none'; + docker_status_message('notice', '<%:Docker actions done.%>'); + document.body.classList.remove('apply-overlay-active'); + window.clearTimeout(tt); + return; + } + loading_msg = resjson?resjson.info:loading_msg + // var delay = isNaN(duration) ? 0 : Math.max(1000 - duration, 0); + var delay =1000 + window.setTimeout(function() { + xhr.get('<%=url("admin/docker/confirm")%>', null, call, uci_apply_timeout * 1000); + },delay); + }; + + var tick = function() { + var now = Date.now(); + + docker_status_message( + 'notice', + '<img src="<%=resource%>/icons/loading.gif" alt="" style="vertical-align:middle" /> <span style="white-space:pre-line; word-break:break-all; font-family: \'Courier New\', Courier, monospace;">' + loading_msg + '</span>' + ); + + tt = window.setTimeout(tick, 200); + ts = now; + }; + + tick(); + /* wait a few seconds for the settings to become effective */ + window.setTimeout(call, Math.max(uci_apply_holdoff * 1000 , 1)); + } + // document.getElementsByTagName("form")[0].addEventListener("submit", (e)=>{ + // uci_confirm_docker() + // }) + + function fnSubmitForm(el){ + if (el.id != "cbid.table.1._new") { + uci_confirm_docker() + } + } + + <% if self.err then -%> + docker_status_message('warning', '<span style="white-space:pre-line; word-break:break-all; font-family: \'Courier New\', Courier, monospace;">'+`<%=self.err%>`+'</span>'); + document.getElementById('docker_apply_overlay').addEventListener( + "click", + (e)=>{ + docker_status_message() + } + ) + <%- end %> + + window.onload= function (){ + var buttons = document.querySelectorAll('input[type="submit"]'); + [].slice.call(buttons).forEach(function (el) { + el.onclick = fnSubmitForm.bind(this, el); + }); + } //]]></script> diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/container.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/container.htm index dab1dee562..9f05d9d589 100644 --- a/applications/luci-app-dockerman/luasrc/view/dockerman/container.htm +++ b/applications/luci-app-dockerman/luasrc/view/dockerman/container.htm @@ -1,12 +1,12 @@ <br> <ul class="cbi-tabmenu"> - <li id="cbi-tab-container_info"><a id="a-cbi-tab-container_info" href=""><%:Info%></a></li> + <li id="cbi-tab-container_info"><a id="a-cbi-tab-container_info" href=""><%:Info%></a></li> <li id="cbi-tab-container_resources"><a id="a-cbi-tab-container_resources" href=""><%:Resources%></a></li> - <li id="cbi-tab-container_stats"><a id="a-cbi-tab-container_stats" href=""><%:Stats%></a></li> - <li id="cbi-tab-container_file"><a id="a-cbi-tab-container_file" href=""><%:File%></a></li> - <li id="cbi-tab-container_console"><a id="a-cbi-tab-container_console" href=""><%:Console%></a></li> - <li id="cbi-tab-container_inspect"><a id="a-cbi-tab-container_inspect" href=""><%:Inspect%></a></li> - <li id="cbi-tab-container_logs"><a id="a-cbi-tab-container_logs" href=""><%:Logs%></a></li> + <li id="cbi-tab-container_stats"><a id="a-cbi-tab-container_stats" href=""><%:Stats%></a></li> + <li id="cbi-tab-container_file"><a id="a-cbi-tab-container_file" href=""><%:File%></a></li> + <li id="cbi-tab-container_console"><a id="a-cbi-tab-container_console" href=""><%:Console%></a></li> + <li id="cbi-tab-container_inspect"><a id="a-cbi-tab-container_inspect" href=""><%:Inspect%></a></li> + <li id="cbi-tab-container_logs"><a id="a-cbi-tab-container_logs" href=""><%:Logs%></a></li> </ul> <script type="text/javascript"> @@ -20,7 +20,8 @@ document.getElementById("a-cbi-tab-container_" + item).href= path[0]+"/admin/docker/container/"+container_id+'/'+item if (action === item) { document.getElementById("cbi-tab-container_" + item).className="cbi-tab" - } else { + } + else { document.getElementById("cbi-tab-container_" + item).className="cbi-tab-disabled" } }) diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/container_console.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/container_console.htm index 636aec9605..7f626b3dc8 100644 --- a/applications/luci-app-dockerman/luasrc/view/dockerman/container_console.htm +++ b/applications/luci-app-dockerman/luasrc/view/dockerman/container_console.htm @@ -1,6 +1,6 @@ <div class="cbi-map"> - <iframe id="terminal" style="width: 100%; min-height: 500px; border: none; border-radius: 3px;"></iframe> + <iframe id="terminal" style="width: 100%; min-height: 500px; border: none; border-radius: 3px;"></iframe> </div> <script type="text/javascript"> - document.getElementById("terminal").src = "http://" + window.location.hostname + ":7682"; + document.getElementById("terminal").src = "http://" + window.location.hostname + ":7682"; </script> diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/container_file.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/container_file.htm index 8be43fbddb..ab5ecdd488 100644 --- a/applications/luci-app-dockerman/luasrc/view/dockerman/container_file.htm +++ b/applications/luci-app-dockerman/luasrc/view/dockerman/container_file.htm @@ -1,63 +1,73 @@ - <div id="upload-container" class="cbi-value cbi-value-last"> - <label class="cbi-value-title" for="archive"><%:Upload%></label> - <div class="cbi-value-field"> - <input type="file" name="upload_archive" accept="application/x-tar" id="upload_archive" /> - </div> - <br> - <label class="cbi-value-title" for="path"><%:Path%></label> - <div class="cbi-value-field"> - <input type="text" class="cbi-input-text" name="path" value="/tmp/" id="path" /> - </div> - <br> - <div class="cbi-value-field"> - <input type="button"" class="btn cbi-button cbi-button-action important" id="upload" name="upload" value="<%:Upload%>" /> - <input type="button"" class="btn cbi-button cbi-button-action important" id="download" name="download" value="<%:Download%>" /> - </div> + <label class="cbi-value-title" for="archive"><%:Upload%></label> + <div class="cbi-value-field"> + <input type="file" name="upload_archive" accept="application/x-tar" id="upload_archive" /> + </div> + <br> + <label class="cbi-value-title" for="path"><%:Path%></label> + <div class="cbi-value-field"> + <input type="text" class="cbi-input-text" name="path" value="/tmp/" id="path" /> + </div> + <br> + <div class="cbi-value-field"> + <input type="button"" class="btn cbi-button cbi-button-action important" id="upload" name="upload" value="<%:Upload%>" /> + <input type="button"" class="btn cbi-button cbi-button-action important" id="download" name="download" value="<%:Download%>" /> + </div> </div> + <script type="text/javascript"> - let btnUpload = document.getElementById('upload') - btnUpload.onclick = function (e) { - let uploadArchive = document.getElementById('upload_archive') - let uploadPath = document.getElementById('path').value - if (!uploadArchive.value || !uploadPath) { - docker_status_message('warning', "<%:Please input the PATH and select the file !%>") - document.getElementById('docker_apply_overlay').addEventListener("click", (e)=>{ - docker_status_message() - }) - return - } - let fileName = uploadArchive.files[0].name - let formData = new FormData() - formData.append('upload-filename', fileName) - formData.append('upload-path', uploadPath) - formData.append('upload-archive', uploadArchive.files[0]) - let xhr = new XMLHttpRequest() - xhr.open("POST", '<%=luci.dispatcher.build_url("admin/docker/container_put_archive")%>/<%=self.container%>', true) - xhr.onload = function() { - if (xhr.status == 200) { - uploadArchive.value = '' - docker_status_message('notice', "<%:Upload Success%>") - } - else { - docker_status_message('warning', "<%:Upload Error%>:" + xhr.statusText) - } - document.getElementById('docker_apply_overlay').addEventListener("click", (e)=>{ - docker_status_message() - }) - } - xhr.send(formData) - } - let btnDownload = document.getElementById('download') - btnDownload.onclick = function (e) { - let downloadPath = document.getElementById('path').value - if (!downloadPath) { - docker_status_message('warning', "<%:Please input the PATH !%>") - document.getElementById('docker_apply_overlay').addEventListener("click", (e)=>{ - docker_status_message() - }) - return - } - window.open('<%=luci.dispatcher.build_url("admin/docker/container_get_archive")%>?id=<%=self.container%>&path=' + encodeURIComponent(downloadPath)) - } + let btnUpload = document.getElementById('upload') + btnUpload.onclick = function (e) { + let uploadArchive = document.getElementById('upload_archive') + let uploadPath = document.getElementById('path').value + if (!uploadArchive.value || !uploadPath) { + docker_status_message('warning', "<%:Please input the PATH and select the file !%>") + document.getElementById('docker_apply_overlay').addEventListener( + "click", + (e)=>{ + docker_status_message() + } + ) + return + } + let fileName = uploadArchive.files[0].name + let formData = new FormData() + formData.append('upload-filename', fileName) + formData.append('upload-path', uploadPath) + formData.append('upload-archive', uploadArchive.files[0]) + let xhr = new XMLHttpRequest() + xhr.open("POST", '<%=luci.dispatcher.build_url("admin/docker/container_put_archive")%>/<%=self.container%>', true) + xhr.onload = function() { + if (xhr.status == 200) { + uploadArchive.value = '' + docker_status_message('notice', "<%:Upload Success%>") + } + else { + docker_status_message('warning', "<%:Upload Error%>:" + xhr.statusText) + } + document.getElementById('docker_apply_overlay').addEventListener( + "click", + (e)=>{ + docker_status_message() + } + ) + } + xhr.send(formData) + } + + let btnDownload = document.getElementById('download') + btnDownload.onclick = function (e) { + let downloadPath = document.getElementById('path').value + if (!downloadPath) { + docker_status_message('warning', "<%:Please input the PATH !%>") + document.getElementById('docker_apply_overlay').addEventListener( + "click", + (e)=>{ + docker_status_message() + } + ) + return + } + window.open('<%=luci.dispatcher.build_url("admin/docker/container_get_archive")%>?id=<%=self.container%>&path=' + encodeURIComponent(downloadPath)) + } </script> diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/container_stats.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/container_stats.htm index b5600e4979..bbcd633e7d 100644 --- a/applications/luci-app-dockerman/luasrc/view/dockerman/container_stats.htm +++ b/applications/luci-app-dockerman/luasrc/view/dockerman/container_stats.htm @@ -2,6 +2,7 @@ let last_bw_tx let last_bw_rx let interval = 3 + function progressbar(v, m, pc, np, f) { m = m || 100 diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/images_import.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/images_import.htm index 6587087b6d..b87759d00f 100644 --- a/applications/luci-app-dockerman/luasrc/view/dockerman/images_import.htm +++ b/applications/luci-app-dockerman/luasrc/view/dockerman/images_import.htm @@ -1,88 +1,104 @@ <input type="text" class="cbi-input-text" name="isrc" placeholder="http://host/image.tar" id="isrc" /> <input type="text" class="cbi-input-text" name="itag" placeholder="repository:tag" id="itag" /> <div style="display: inline-block;"> - <input type="button"" class="btn cbi-button cbi-button-add" id="btnimport" name="import" value="<%:Import%>" /> - <input type="file" id="file_import" style="visibility:hidden; position: absolute;top: 0px; left: 0px;" /> + <input type="button"" class="btn cbi-button cbi-button-add" id="btnimport" name="import" value="<%:Import%>" /> + <input type="file" id="file_import" style="visibility:hidden; position: absolute;top: 0px; left: 0px;" /> </div> <script type="text/javascript"> - let btnImport = document.getElementById('btnimport') - let valISrc = document.getElementById('isrc') - let valITag = document.getElementById('itag') - btnImport.onclick = function (e) { - if (valISrc.value == "") { - document.getElementById("file_import").click() - return - } else { - let formData = new FormData() - formData.append('src', valISrc.value) - formData.append('tag', valITag.value) - let xhr = new XMLHttpRequest() - uci_confirm_docker() - xhr.open("POST", "<%=luci.dispatcher.build_url('admin/docker/images_import')%>", true) - xhr.onload = function () { - location.reload() - } - xhr.send(formData) - } - } - let fileimport = document.getElementById('file_import') - fileimport.onchange = function (e) { - let fileimport = document.getElementById('file_import') - if (!fileimport.value) { - return - } - let valITag = document.getElementById('itag') - let fileName = fileimport.files[0].name - let formData = new FormData() - formData.append('upload-filename', fileName) - formData.append('tag', valITag.value) - formData.append('upload-archive', fileimport.files[0]) - let xhr = new XMLHttpRequest() - uci_confirm_docker() - xhr.open("POST", "<%=luci.dispatcher.build_url('admin/docker/images_import')%>", true) - xhr.onload = function () { - fileimport.value = '' - location.reload() - } - xhr.send(formData) - } + let btnImport = document.getElementById('btnimport') + let valISrc = document.getElementById('isrc') + let valITag = document.getElementById('itag') + btnImport.onclick = function (e) { + if (valISrc.value == "") { + document.getElementById("file_import").click() + return + } + else { + let formData = new FormData() + formData.append('src', valISrc.value) + formData.append('tag', valITag.value) + let xhr = new XMLHttpRequest() + uci_confirm_docker() + xhr.open("POST", "<%=luci.dispatcher.build_url('admin/docker/images_import')%>", true) + xhr.onload = function () { + location.reload() + } + xhr.send(formData) + } + } - let new_tag = function (image_id) { - let new_tag = prompt("<%:New tag%>\n<%:Image%>" + "ID: " + image_id + "\n<%:Please input new tag%>:", "") - if (new_tag) { - (new XHR()).post("<%=luci.dispatcher.build_url('admin/docker/images_tag')%>", - { id: image_id, tag: new_tag }, - function (r) { - if (r.status == 201) { - location.reload() - } - else { - docker_status_message('warning', 'Image: untagging ' + tag + '...fail code:' + r.status + r.statusText); - document.getElementById('docker_apply_overlay').addEventListener("click", (e)=>{ - docker_status_message() - }) - } - }) - } - } + let fileimport = document.getElementById('file_import') + fileimport.onchange = function (e) { + let fileimport = document.getElementById('file_import') + if (!fileimport.value) { + return + } + let valITag = document.getElementById('itag') + let fileName = fileimport.files[0].name + let formData = new FormData() + formData.append('upload-filename', fileName) + formData.append('tag', valITag.value) + formData.append('upload-archive', fileimport.files[0]) + let xhr = new XMLHttpRequest() + uci_confirm_docker() + xhr.open("POST", "<%=luci.dispatcher.build_url('admin/docker/images_import')%>", true) + xhr.onload = function () { + fileimport.value = '' + location.reload() + } + xhr.send(formData) + } - let un_tag = function (tag) { - if (tag.match("<none>")) return - if (confirm("<%:Remove tag%>: " + tag + " ?")) { - (new XHR()).post("<%=luci.dispatcher.build_url('admin/docker/images_untag')%>", - { tag: tag }, - function (r) { - if (r.status == 200) { - location.reload() - } - else { - docker_status_message('warning', 'Image: untagging ' + tag + '...fail code:' + r.status + r.statusText); - document.getElementById('docker_apply_overlay').addEventListener("click", (e)=>{ - docker_status_message() - }) - } - }) - } - } + let new_tag = function (image_id) { + let new_tag = prompt("<%:New tag%>\n<%:Image%>" + "ID: " + image_id + "\n<%:Please input new tag%>:", "") + if (new_tag) { + (new XHR()).post("<%=luci.dispatcher.build_url('admin/docker/images_tag')%>", + { + id: image_id, + tag: new_tag + }, + function (r) { + if (r.status == 201) { + location.reload() + } + else { + docker_status_message('warning', 'Image: untagging ' + tag + '...fail code:' + r.status + r.statusText); + document.getElementById('docker_apply_overlay').addEventListener( + "click", + (e)=>{ + docker_status_message() + } + ) + } + } + ) + } + } + + let un_tag = function (tag) { + if (tag.match("<none>")) + return + if (confirm("<%:Remove tag%>: " + tag + " ?")) { + (new XHR()).post("<%=luci.dispatcher.build_url('admin/docker/images_untag')%>", + { + tag: tag + }, + function (r) { + if (r.status == 200) { + location.reload() + } + else { + docker_status_message('warning', 'Image: untagging ' + tag + '...fail code:' + r.status + r.statusText); + document.getElementById('docker_apply_overlay').addEventListener( + "click", + (e)=>{ + docker_status_message() + } + ) + } + } + ) + } + } </script> diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/images_load.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/images_load.htm index 3ea4bd0663..9a90a90f03 100644 --- a/applications/luci-app-dockerman/luasrc/view/dockerman/images_load.htm +++ b/applications/luci-app-dockerman/luasrc/view/dockerman/images_load.htm @@ -1,29 +1,30 @@ <div style="display: inline-block;"> - <input type="button"" class="btn cbi-button cbi-button-add" id="btnload" name="load" value="<%:Load%>" /> - <input type="file" id="file_load" style="visibility:hidden; position: absolute;top: 0px; left: 0px;" accept="application/x-tar" /> + <input type="button"" class="btn cbi-button cbi-button-add" id="btnload" name="load" value="<%:Load%>" /> + <input type="file" id="file_load" style="visibility:hidden; position: absolute;top: 0px; left: 0px;" accept="application/x-tar" /> </div> <script type="text/javascript"> - let btnLoad = document.getElementById('btnload') - btnLoad.onclick = function (e) { - document.getElementById("file_load").click() - e.preventDefault() - } - let fileLoad = document.getElementById('file_load') - fileLoad.onchange = function(e){ - let fileLoad = document.getElementById('file_load') - if (!fileLoad.value) { - return - } - let fileName = fileLoad.files[0].name - let formData = new FormData() - formData.append('upload-filename', fileName) - formData.append('upload-archive', fileLoad.files[0]) - let xhr = new XMLHttpRequest() - uci_confirm_docker() - xhr.open("POST", '<%=luci.dispatcher.build_url("admin/docker/images_load")%>', true) - xhr.onload = function() { - location.reload() - } - xhr.send(formData) - } + let btnLoad = document.getElementById('btnload') + btnLoad.onclick = function (e) { + document.getElementById("file_load").click() + e.preventDefault() + } + + let fileLoad = document.getElementById('file_load') + fileLoad.onchange = function(e){ + let fileLoad = document.getElementById('file_load') + if (!fileLoad.value) { + return + } + let fileName = fileLoad.files[0].name + let formData = new FormData() + formData.append('upload-filename', fileName) + formData.append('upload-archive', fileLoad.files[0]) + let xhr = new XMLHttpRequest() + uci_confirm_docker() + xhr.open("POST", '<%=luci.dispatcher.build_url("admin/docker/images_load")%>', true) + xhr.onload = function() { + location.reload() + } + xhr.send(formData) + } </script> diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/newcontainer_resolve.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/newcontainer_resolve.htm index 1b130b4ec6..dd6cafa062 100644 --- a/applications/luci-app-dockerman/luasrc/view/dockerman/newcontainer_resolve.htm +++ b/applications/luci-app-dockerman/luasrc/view/dockerman/newcontainer_resolve.htm @@ -1,95 +1,102 @@ <style type="text/css"> - #dialog_reslov { - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; - background: rgba(0, 0, 0, 0.7); - display: none; - z-index: 20000; - } - - #dialog_reslov .dialog_box { - position: relative; - background: rgba(255, 255, 255); - top: 10%; - width: 50%; - margin: auto; - display: flex; - flex-wrap: wrap; - height:auto; - align-items: center; - } - - #dialog_reslov .dialog_line { - margin-top: .5em; - margin-bottom: .5em; - margin-left: 2em; - margin-right: 2em; - } - - #dialog_reslov .dialog_box>h4, - #dialog_reslov .dialog_box>p, - #dialog_reslov .dialog_box>div { - flex-basis: 100%; - } - - #dialog_reslov .dialog_box>img { - margin-right: 1em; - flex-basis: 32px; - } - - body.dialog-reslov-active { - overflow: hidden; - height: 100vh; - } - - body.dialog-reslov-active #dialog_reslov { - display: block; - } + #dialog_reslov { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + background: rgba(0, 0, 0, 0.7); + display: none; + z-index: 20000; + } + + #dialog_reslov .dialog_box { + position: relative; + background: rgba(255, 255, 255); + top: 10%; + width: 50%; + margin: auto; + display: flex; + flex-wrap: wrap; + height:auto; + align-items: center; + } + + #dialog_reslov .dialog_line { + margin-top: .5em; + margin-bottom: .5em; + margin-left: 2em; + margin-right: 2em; + } + + #dialog_reslov .dialog_box>h4, + #dialog_reslov .dialog_box>p, + #dialog_reslov .dialog_box>div { + flex-basis: 100%; + } + + #dialog_reslov .dialog_box>img { + margin-right: 1em; + flex-basis: 32px; + } + + body.dialog-reslov-active { + overflow: hidden; + height: 100vh; + } + + body.dialog-reslov-active #dialog_reslov { + display: block; + } </style> + <script type="text/javascript"> - function close_reslov_dialog() { - document.body.classList.remove('dialog-reslov-active') - document.documentElement.style.overflowY = 'scroll' - } - - function reslov_container() { - let s = document.getElementById('cmd-line-status') - if (!s) return - let cmd_line = document.getElementById("dialog_reslov_text").value; - if (cmd_line == null || cmd_line == "") { - return - } - cmd_line = cmd_line.replace(/(^\s*)/g,"") - if (!cmd_line.match(/^docker\s+(run|create)/)) { - s.innerHTML = "<font color='red'><%:Command line Error%></font>" - return - } - let reg_space = /\s+/g - let reg_muti_line= /\\\s*\n/g - // reg_rem =/(?<!\\)`#.+(?<!\\)`/g // the command has `# ` - let reg_rem =/`#.+`/g// the command has `# ` - cmd_line = cmd_line.replace(/^docker\s+(run|create)/,"DOCKERCLI").replace(reg_rem, " ").replace(reg_muti_line, " ").replace(reg_space, " ") - console.log(cmd_line) - window.location.href = '<%=luci.dispatcher.build_url("admin/docker/newcontainer")%>/' + encodeURI(cmd_line) - } - - function clear_text(){ - let s = document.getElementById('cmd-line-status') - s.innerHTML = "" - } - - function show_reslov_dialog() { - document.getElementById('dialog_reslov') || document.body.insertAdjacentHTML("beforeend", '<div id="dialog_reslov"><div class="dialog_box"><div class="dialog_line"></div><div class="dialog_line"><span><%:Plese input <docker create/run> command line:%></span><br><span id="cmd-line-status"></span></div><div class="dialog_line"><textarea class="cbi-input-textarea" id="dialog_reslov_text" style="width: 100%; height:100%;" rows="15" onkeyup="clear_text()"></textarea></div><div class="dialog_line" style="text-align: right;"><input type="button" class="btn cbi-button cbi-button-apply" type="submit" value="<%:Submit%>" onclick="reslov_container()" /> <input type="button" class="btn cbi-button cbi-button-reset" type="reset" value="<%:Cancel%>" onclick="close_reslov_dialog()" /></div><div class="dialog_line"></div></div></div>') - document.body.classList.add('dialog-reslov-active') - let s = document.getElementById('cmd-line-status') - s.innerHTML = "" - document.documentElement.style.overflowY = 'hidden' - } + function close_reslov_dialog() { + document.body.classList.remove('dialog-reslov-active') + document.documentElement.style.overflowY = 'scroll' + } + + function reslov_container() { + let s = document.getElementById('cmd-line-status') + + if (!s) + return + + let cmd_line = document.getElementById("dialog_reslov_text").value; + if (cmd_line == null || cmd_line == "") { + return + } + + cmd_line = cmd_line.replace(/(^\s*)/g,"") + if (!cmd_line.match(/^docker\s+(run|create)/)) { + s.innerHTML = "<font color='red'><%:Command line Error%></font>" + return + } + + let reg_space = /\s+/g + let reg_muti_line= /\\\s*\n/g + // reg_rem =/(?<!\\)`#.+(?<!\\)`/g // the command has `# ` + let reg_rem =/`#.+`/g// the command has `# ` + cmd_line = cmd_line.replace(/^docker\s+(run|create)/,"DOCKERCLI").replace(reg_rem, " ").replace(reg_muti_line, " ").replace(reg_space, " ") + console.log(cmd_line) + window.location.href = '<%=luci.dispatcher.build_url("admin/docker/newcontainer")%>/' + encodeURI(cmd_line) + } + + function clear_text(){ + let s = document.getElementById('cmd-line-status') + s.innerHTML = "" + } + + function show_reslov_dialog() { + document.getElementById('dialog_reslov') || document.body.insertAdjacentHTML("beforeend", '<div id="dialog_reslov"><div class="dialog_box"><div class="dialog_line"></div><div class="dialog_line"><span><%:Plese input <docker create/run> command line:%></span><br><span id="cmd-line-status"></span></div><div class="dialog_line"><textarea class="cbi-input-textarea" id="dialog_reslov_text" style="width: 100%; height:100%;" rows="15" onkeyup="clear_text()"></textarea></div><div class="dialog_line" style="text-align: right;"><input type="button" class="btn cbi-button cbi-button-apply" type="submit" value="<%:Submit%>" onclick="reslov_container()" /> <input type="button" class="btn cbi-button cbi-button-reset" type="reset" value="<%:Cancel%>" onclick="close_reslov_dialog()" /></div><div class="dialog_line"></div></div></div>') + document.body.classList.add('dialog-reslov-active') + let s = document.getElementById('cmd-line-status') + s.innerHTML = "" + document.documentElement.style.overflowY = 'hidden' + } </script> <%+cbi/valueheader%> + <input type="button" class="btn cbi-button cbi-button-apply" value="<%:Command line%>" onclick="show_reslov_dialog()" /> <%+cbi/valuefooter%> diff --git a/applications/luci-app-dockerman/luasrc/view/dockerman/overview.htm b/applications/luci-app-dockerman/luasrc/view/dockerman/overview.htm index 474009dfdc..e491fc5126 100644 --- a/applications/luci-app-dockerman/luasrc/view/dockerman/overview.htm +++ b/applications/luci-app-dockerman/luasrc/view/dockerman/overview.htm @@ -1,280 +1,197 @@ <style> - /*! -Pure v1.0.1 -Copyright 2013 Yahoo! -Licensed under the BSD License. -https://github.com/pure-css/pure/blob/master/LICENSE.md -*/ - .pure-g { - letter-spacing: -.31em; - text-rendering: optimizespeed; - font-family: FreeSans, Arimo, "Droid Sans", Helvetica, Arial, sans-serif; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-box-orient: horizontal; - -webkit-box-direction: normal; - -webkit-flex-flow: row wrap; - -ms-flex-flow: row wrap; - flex-flow: row wrap; - -webkit-align-content: flex-start; - -ms-flex-line-pack: start; - align-content: flex-start - } + /*! + Pure v1.0.1 + Copyright 2013 Yahoo! + Licensed under the BSD License. + https://github.com/pure-css/pure/blob/master/LICENSE.md + */ + .pure-g { + letter-spacing: -.31em; + text-rendering: optimizespeed; + font-family: FreeSans, Arimo, "Droid Sans", Helvetica, Arial, sans-serif; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -webkit-flex-flow: row wrap; + -ms-flex-flow: row wrap; + flex-flow: row wrap; + -webkit-align-content: flex-start; + -ms-flex-line-pack: start; + align-content: flex-start + } - .pure-u { - display: inline-block; - zoom: 1; - letter-spacing: normal; - word-spacing: normal; - vertical-align: top; - text-rendering: auto - } + .pure-u { + display: inline-block; + zoom: 1; + letter-spacing: normal; + word-spacing: normal; + vertical-align: top; + text-rendering: auto + } - .pure-g [class*=pure-u] { - font-family: sans-serif - } + .pure-g [class*=pure-u] { + font-family: sans-serif + } - .pure-u-1-4, - .pure-u-2-5, - .pure-u-3-5 { - display: inline-block; - zoom: 1; - letter-spacing: normal; - word-spacing: normal; - vertical-align: top; - text-rendering: auto - } + .pure-u-1-4, + .pure-u-2-5, + .pure-u-3-5 { + display: inline-block; + zoom: 1; + letter-spacing: normal; + word-spacing: normal; + vertical-align: top; + text-rendering: auto + } - .pure-u-1-4 { - width: 25% - } + .pure-u-1-4 { + width: 25% + } - .pure-u-2-5 { - width: 40% - } + .pure-u-2-5 { + width: 40% + } - .pure-u-3-5 { - width: 60% - } + .pure-u-3-5 { + width: 60% + } - .status { - margin: 1rem -0.5rem 1rem -0.5rem; - } + .status { + margin: 1rem -0.5rem 1rem -0.5rem; + } - .block { - margin: 0.5rem 0.5rem; - padding: 0; - font-weight: normal; - font-style: normal; - line-height: 1; - font-family: inherit; - min-width: inherit; - overflow-x: auto; - overflow-y: hidden; - border: 1px solid rgba(0, 0, 0, .05); - border-radius: .375rem; - box-shadow: 0 0 2rem 0 rgba(136, 152, 170, .15); - } + .block { + margin: 0.5rem 0.5rem; + padding: 0; + font-weight: normal; + font-style: normal; + line-height: 1; + font-family: inherit; + min-width: inherit; + overflow-x: auto; + overflow-y: hidden; + border: 1px solid rgba(0, 0, 0, .05); + border-radius: .375rem; + box-shadow: 0 0 2rem 0 rgba(136, 152, 170, .15); + } - .img-con { - margin: 1rem; - min-width: 4rem; - max-width: 4rem; - min-height: 4rem; - max-height: 4rem; - } + .img-con { + margin: 1rem; + min-width: 4rem; + max-width: 4rem; + min-height: 4rem; + max-height: 4rem; + } - .block h4 { - font-size: .8125rem; - font-weight: 600; - margin: 1rem; - color: #8898aa !important; - line-height: 1.8em; - } + .block h4 { + font-size: .8125rem; + font-weight: 600; + margin: 1rem; + color: #8898aa !important; + line-height: 1.8em; + } - .cbi-section-table-cell { - position: relative; - } + .cbi-section-table-cell { + position: relative; + } - @media screen and (max-width: 700px) { - .pure-u-1-4 { - width: 50%; - } + @media screen and (max-width: 700px) { + .pure-u-1-4 { + width: 50%; + } - .cbi-button-add { - position: fixed; - padding: 0.3rem 0.5rem; - z-index: 1000; - width: 50px !important; - height: 50px; - bottom: 90px; - right: 5px; - font-size: 16px; - border-radius: 50%; - display: block; - background-color: #fb6340 !important; - border-color: #fb6340 !important; - box-shadow: 0 0 1rem 0 rgba(136, 152, 170, .75); - } - } + .cbi-button-add { + position: fixed; + padding: 0.3rem 0.5rem; + z-index: 1000; + width: 50px !important; + height: 50px; + bottom: 90px; + right: 5px; + font-size: 16px; + border-radius: 50%; + display: block; + background-color: #fb6340 !important; + border-color: #fb6340 !important; + box-shadow: 0 0 1rem 0 rgba(136, 152, 170, .75); + } + } </style> <div class="pure-g status"> - <div class="pure-u-1-4"> - <div class="block pure-g"> - <div class="pure-u-2-5"> - <div class="img-con"> - <svg role="img" viewBox="0 0 24 24"> - <title>Docker icon</title> - <path - d="M4.82 17.275c-.684 0-1.304-.56-1.304-1.24s.56-1.243 1.305-1.243c.748 0 1.31.56 1.31 1.242s-.622 1.24-1.305 1.24zm16.012-6.763c-.135-.992-.75-1.8-1.56-2.42l-.315-.25-.254.31c-.494.56-.69 1.553-.63 2.295.06.562.24 1.12.554 1.554-.254.13-.568.25-.81.377-.57.187-1.124.25-1.68.25H.097l-.06.37c-.12 1.182.06 2.42.562 3.54l.244.435v.06c1.5 2.483 4.17 3.6 7.078 3.6 5.594 0 10.182-2.42 12.357-7.633 1.425.062 2.864-.31 3.54-1.676l.18-.31-.3-.187c-.81-.494-1.92-.56-2.85-.31l-.018.002zm-8.008-.992h-2.428v2.42h2.43V9.518l-.002.003zm0-3.043h-2.428v2.42h2.43V6.48l-.002-.003zm0-3.104h-2.428v2.42h2.43v-2.42h-.002zm2.97 6.147H13.38v2.42h2.42V9.518l-.007.003zm-8.998 0H4.383v2.42h2.422V9.518l-.01.003zm3.03 0h-2.4v2.42H9.84V9.518l-.015.003zm-6.03 0H1.4v2.42h2.428V9.518l-.03.003zm6.03-3.043h-2.4v2.42H9.84V6.48l-.015-.003zm-3.045 0H4.387v2.42H6.8V6.48l-.016-.003z" /> - </svg> - </div> - </div> - <div class="pure-u-3-5"> - <h4 style="text-align: right; font-size: 1rem"><%:Containers%></h4> - <h4 style="text-align: right;"> - <%- if self.containers_total ~= "-" then -%><a href='<%=luci.dispatcher.build_url("admin/docker/containers")%>'><%- end -%> - <span style="font-size: 2rem; color: #2dce89;"><%=self.containers_running%></span> - <span style="font-size: 1rem; color: #8898aa !important;">/<%=self.containers_total%></span> - <%- if self.containers_total ~= "-" then -%></a><%- end -%> - </h4> - </div> - </div> - </div> - <div class="pure-u-1-4"> - <div class="block pure-g"> - <div class="pure-u-2-5"> - <div class="img-con"> - <svg id="icon-hub" viewBox="0 -4 42 50" stroke-width="2" fill-rule="nonzero" width="100%" height="100%"> - <path - d="M37.176371,36.2324812 C37.1920117,36.8041095 36.7372743,37.270685 36.1684891,37.270685 L3.74335204,37.2703476 C3.17827583,37.2703476 2.72400056,36.8091818 2.72400056,36.2397767 L2.72400056,19.6131383 C1.4312007,18.4881431 0.662551336,16.8884326 0.662551336,15.1618249 L0.664207893,14.69503 C0.63774183,14.4532127 0.650524255,14.2942438 0.711604827,14.1238231 L5.10793246,1.20935468 C5.24853286,0.797020623 5.63848594,0.511627907 6.06681069,0.511627907 L34.0728364,0.511627907 C34.5091607,0.511627907 34.889927,0.793578201 35.0316653,1.20921034 L39.4428567,14.1234095 C39.4871296,14.273204 39.5020782,14.4249444 39.4884726,14.5493649 L39.4884726,15.1505835 C39.4884726,16.9959517 38.6190601,18.6883031 37.1764746,19.7563084 L37.176371,36.2324812 Z M35.1376208,35.209311 L35.1376208,20.7057152 C34.7023924,20.8097593 34.271333,20.8633641 33.8336069,20.8633641 C32.0046019,20.8633641 30.3013756,19.9547008 29.2437221,18.4771538 C28.1860473,19.954695 26.4828515,20.8633641 24.6538444,20.8633641 C22.824803,20.8633641 21.1216155,19.9547157 20.0639591,18.4771544 C19.0062842,19.9546953 17.3030887,20.8633641 15.4740818,20.8633641 C13.6450404,20.8633641 11.9418529,19.9547157 10.8841965,18.4771544 C9.82652161,19.9546953 8.12332608,20.8633641 6.29431919,20.8633641 C5.76735555,20.8633641 5.24095778,20.7883418 4.73973398,20.644674 L4.73973398,35.209311 L35.1376208,35.209311 Z M30.2720226,15.6557626 C30.5154632,17.4501192 32.0503909,18.8018554 33.845083,18.8018554 C35.7286794,18.8018554 37.285413,17.3395134 37.4474599,15.4751932 L30.2280765,15.4751932 C30.2470638,15.532987 30.2617919,15.5932958 30.2720226,15.6557626 Z M21.0484306,15.4751932 C21.0674179,15.532987 21.0821459,15.5932958 21.0923767,15.6557626 C21.3358173,17.4501192 22.8707449,18.8018554 24.665437,18.8018554 C26.4601001,18.8018554 27.9950169,17.4501481 28.2378191,15.6611556 C28.2451225,15.5981318 28.2590045,15.5358056 28.2787375,15.4751932 L21.0484306,15.4751932 Z M11.9238102,15.6557626 C12.1672508,17.4501192 13.7021785,18.8018554 15.4968705,18.8018554 C17.2915336,18.8018554 18.8264505,17.4501481 19.0692526,15.6611556 C19.0765561,15.5981318 19.0904381,15.5358056 19.110171,15.4751932 L11.8798641,15.4751932 C11.8988514,15.532987 11.9135795,15.5932958 11.9238102,15.6557626 Z M6.31682805,18.8018317 C8.11149114,18.8018317 9.64640798,17.4501244 9.88921012,15.6611319 C9.89651357,15.5981081 9.91039559,15.5357819 9.93012856,15.4751696 L2.70318796,15.4751696 C2.86612006,17.3346852 4.42809696,18.8018317 6.31682805,18.8018317 Z M3.09670082,13.4139924 L37.04257,13.4139924 L33.3489482,2.57204736 L6.80119239,2.57204736 L3.09670082,13.4139924 Z" - id="Fill-1"></path> - <rect id="Rectangle-3" x="14" y="26" width="6" height="10"></rect> - <path d="M20,26 L20,36 L26,36 L26,26 L20,26 Z" id="Rectangle-3"></path> - </svg> - </div> - </div> - <div class="pure-u-3-5"> - <h4 style="text-align: right; font-size: 1rem"><%:Images%></h4> - <h4 style="text-align: right;"> - <%- if self.images_total ~= "-" then -%><a href='<%=luci.dispatcher.build_url("admin/docker/images")%>'><%- end -%> - <span style="font-size: 2rem; color: #2dce89;"><%=self.images_used%></span> - <span style="font-size: 1rem; color: #8898aa !important;">/<%=self.images_total%></span> - <%- if self.images_total ~= "-" then -%></a><%- end -%> - </h4> - </div> - </div> - </div> - <div class="pure-u-1-4"> - <div class="block pure-g"> - <div class="pure-u-2-5"> - <div class="img-con"> - <svg version="1.1" x="0px" y="0px" width="100%" height="100%" viewBox="0 0 48.723 48.723" xml:space="preserve"> - <path d="M7.452,24.152h3.435v5.701h0.633c0.001,0,0.001,0,0.002,0h0.636v-5.701h3.51v-1.059h17.124v1.104h3.178v5.656h0.619 - c0,0,0,0,0.002,0h0.619v-5.656h3.736v-0.856c0-0.012,0.006-0.021,0.006-0.032c0-0.072,0-0.143,0-0.215h5.721v-1.316h-5.721 - c0-0.054,0-0.108,0-0.164c0-0.011-0.006-0.021-0.006-0.032v-0.832h-8.154v1.028h-7.911v-2.652h-0.689c-0.001,0-0.001,0-0.002,0 - h-0.678v2.652h-7.846v-1.104H7.452v1.104H1.114v1.316h6.338V24.152z" /> - <path - d="M21.484,16.849h5.204v-2.611h7.133V1.555H14.588v12.683h6.896V16.849z M16.537,12.288V3.505h15.335v8.783H16.537z" /> - <rect x="18.682" y="16.898" width="10.809" height="0.537" /> - <path - d="M0,43.971h6.896v2.611H12.1v-2.611h7.134V31.287H0V43.971z M1.95,33.236h15.334v8.785H1.95V33.236z" /> - <rect x="4.095" y="46.631" width="10.808" height="0.537" /> - <path - d="M29.491,30.994v12.684h6.895v2.611h5.205v-2.611h7.133V30.994H29.491z M46.774,41.729H31.44v-8.783h15.334V41.729z" /> - <rect x="33.584" y="46.338" width="10.809" height="0.537" /> - </svg> - </div> - </div> - <div class="pure-u-3-5"> - <h4 style="text-align: right; font-size: 1rem"><%:Networks%></h4> - <h4 style="text-align: right;"> - <%- if self.networks_total ~= "-" then -%><a href='<%=luci.dispatcher.build_url("admin/docker/networks")%>'><%- end -%> - <span style="font-size: 2rem; color: #2dce89;"><%=self.networks_total%></span> - <!-- <span style="font-size: 1rem; color: #8898aa !important;">/20</span> --> - <%- if self.networks_total ~= "-" then -%></a><%- end -%> - </h4> - </div> - </div> - </div> - <div class="pure-u-1-4"> - <div class="block pure-g"> - <div class="pure-u-2-5"> - <div class="img-con"> - <svg x="0px" y="0px" viewBox="0 0 55 55" style="enable-background:new 0 0 55 55;" xml:space="preserve"> - <path - d="M52.354,8.51C51.196,4.22,42.577,0,27.5,0C12.423,0,3.803,4.22,2.646,8.51C2.562,8.657,2.5,8.818,2.5,9v0.5V21v0.5V22v11 - v0.5V34v12c0,0.162,0.043,0.315,0.117,0.451C3.798,51.346,14.364,55,27.5,55c13.106,0,23.655-3.639,24.875-8.516 - C52.455,46.341,52.5,46.176,52.5,46V34v-0.5V33V22v-0.5V21V9.5V9C52.5,8.818,52.438,8.657,52.354,8.51z M50.421,33.985 - c-0.028,0.121-0.067,0.241-0.116,0.363c-0.04,0.099-0.089,0.198-0.143,0.297c-0.067,0.123-0.142,0.246-0.231,0.369 - c-0.066,0.093-0.141,0.185-0.219,0.277c-0.111,0.131-0.229,0.262-0.363,0.392c-0.081,0.079-0.17,0.157-0.26,0.236 - c-0.164,0.143-0.335,0.285-0.526,0.426c-0.082,0.061-0.17,0.12-0.257,0.18c-0.226,0.156-0.462,0.311-0.721,0.463 - c-0.068,0.041-0.141,0.08-0.212,0.12c-0.298,0.168-0.609,0.335-0.945,0.497c-0.043,0.021-0.088,0.041-0.132,0.061 - c-0.375,0.177-0.767,0.351-1.186,0.519c-0.012,0.005-0.024,0.009-0.036,0.014c-2.271,0.907-5.176,1.67-8.561,2.17 - c-0.017,0.002-0.034,0.004-0.051,0.007c-0.658,0.097-1.333,0.183-2.026,0.259c-0.113,0.012-0.232,0.02-0.346,0.032 - c-0.605,0.063-1.217,0.121-1.847,0.167c-0.288,0.021-0.59,0.031-0.883,0.049c-0.474,0.028-0.943,0.059-1.429,0.076 - C29.137,40.984,28.327,41,27.5,41s-1.637-0.016-2.432-0.044c-0.486-0.017-0.955-0.049-1.429-0.076 - c-0.293-0.017-0.595-0.028-0.883-0.049c-0.63-0.046-1.242-0.104-1.847-0.167c-0.114-0.012-0.233-0.02-0.346-0.032 - c-0.693-0.076-1.368-0.163-2.026-0.259c-0.017-0.002-0.034-0.004-0.051-0.007c-3.385-0.5-6.29-1.263-8.561-2.17 - c-0.012-0.004-0.024-0.009-0.036-0.014c-0.419-0.168-0.812-0.342-1.186-0.519c-0.043-0.021-0.089-0.041-0.132-0.061 - c-0.336-0.162-0.647-0.328-0.945-0.497c-0.07-0.04-0.144-0.079-0.212-0.12c-0.259-0.152-0.495-0.307-0.721-0.463 - c-0.086-0.06-0.175-0.119-0.257-0.18c-0.191-0.141-0.362-0.283-0.526-0.426c-0.089-0.078-0.179-0.156-0.26-0.236 - c-0.134-0.13-0.252-0.26-0.363-0.392c-0.078-0.092-0.153-0.184-0.219-0.277c-0.088-0.123-0.163-0.246-0.231-0.369 - c-0.054-0.099-0.102-0.198-0.143-0.297c-0.049-0.121-0.088-0.242-0.116-0.363C4.541,33.823,4.5,33.661,4.5,33.5 - c0-0.113,0.013-0.226,0.031-0.338c0.025-0.151,0.011-0.302-0.031-0.445v-7.424c0.028,0.026,0.063,0.051,0.092,0.077 - c0.218,0.192,0.44,0.383,0.69,0.567C9.049,28.786,16.582,31,27.5,31c10.872,0,18.386-2.196,22.169-5.028 - c0.302-0.22,0.574-0.447,0.83-0.678l0.001-0.001v7.424c-0.042,0.143-0.056,0.294-0.031,0.445c0.019,0.112,0.031,0.225,0.031,0.338 - C50.5,33.661,50.459,33.823,50.421,33.985z M50.5,13.293v7.424c-0.042,0.143-0.056,0.294-0.031,0.445 - c0.019,0.112,0.031,0.225,0.031,0.338c0,0.161-0.041,0.323-0.079,0.485c-0.028,0.121-0.067,0.241-0.116,0.363 - c-0.04,0.099-0.089,0.198-0.143,0.297c-0.067,0.123-0.142,0.246-0.231,0.369c-0.066,0.093-0.141,0.185-0.219,0.277 - c-0.111,0.131-0.229,0.262-0.363,0.392c-0.081,0.079-0.17,0.157-0.26,0.236c-0.164,0.143-0.335,0.285-0.526,0.426 - c-0.082,0.061-0.17,0.12-0.257,0.18c-0.226,0.156-0.462,0.311-0.721,0.463c-0.068,0.041-0.141,0.08-0.212,0.12 - c-0.298,0.168-0.609,0.335-0.945,0.497c-0.043,0.021-0.088,0.041-0.132,0.061c-0.375,0.177-0.767,0.351-1.186,0.519 - c-0.012,0.005-0.024,0.009-0.036,0.014c-2.271,0.907-5.176,1.67-8.561,2.17c-0.017,0.002-0.034,0.004-0.051,0.007 - c-0.658,0.097-1.333,0.183-2.026,0.259c-0.113,0.012-0.232,0.02-0.346,0.032c-0.605,0.063-1.217,0.121-1.847,0.167 - c-0.288,0.021-0.59,0.031-0.883,0.049c-0.474,0.028-0.943,0.059-1.429,0.076C29.137,28.984,28.327,29,27.5,29 - s-1.637-0.016-2.432-0.044c-0.486-0.017-0.955-0.049-1.429-0.076c-0.293-0.017-0.595-0.028-0.883-0.049 - c-0.63-0.046-1.242-0.104-1.847-0.167c-0.114-0.012-0.233-0.02-0.346-0.032c-0.693-0.076-1.368-0.163-2.026-0.259 - c-0.017-0.002-0.034-0.004-0.051-0.007c-3.385-0.5-6.29-1.263-8.561-2.17c-0.012-0.004-0.024-0.009-0.036-0.014 - c-0.419-0.168-0.812-0.342-1.186-0.519c-0.043-0.021-0.089-0.041-0.132-0.061c-0.336-0.162-0.647-0.328-0.945-0.497 - c-0.07-0.04-0.144-0.079-0.212-0.12c-0.259-0.152-0.495-0.307-0.721-0.463c-0.086-0.06-0.175-0.119-0.257-0.18 - c-0.191-0.141-0.362-0.283-0.526-0.426c-0.089-0.078-0.179-0.156-0.26-0.236c-0.134-0.13-0.252-0.26-0.363-0.392 - c-0.078-0.092-0.153-0.184-0.219-0.277c-0.088-0.123-0.163-0.246-0.231-0.369c-0.054-0.099-0.102-0.198-0.143-0.297 - c-0.049-0.121-0.088-0.242-0.116-0.363C4.541,21.823,4.5,21.661,4.5,21.5c0-0.113,0.013-0.226,0.031-0.338 - c0.025-0.151,0.011-0.302-0.031-0.445v-7.424c0.12,0.109,0.257,0.216,0.387,0.324c0.072,0.06,0.139,0.12,0.215,0.18 - c0.3,0.236,0.624,0.469,0.975,0.696c0.073,0.047,0.155,0.093,0.231,0.14c0.294,0.183,0.605,0.362,0.932,0.538 - c0.121,0.065,0.242,0.129,0.367,0.193c0.365,0.186,0.748,0.367,1.151,0.542c0.066,0.029,0.126,0.059,0.193,0.087 - c0.469,0.199,0.967,0.389,1.485,0.573c0.143,0.051,0.293,0.099,0.44,0.149c0.412,0.139,0.838,0.272,1.279,0.401 - c0.159,0.046,0.315,0.094,0.478,0.138c0.585,0.162,1.189,0.316,1.823,0.458c0.087,0.02,0.181,0.036,0.269,0.055 - c0.559,0.122,1.139,0.235,1.735,0.341c0.202,0.036,0.407,0.07,0.613,0.104c0.567,0.093,1.151,0.178,1.75,0.256 - c0.154,0.02,0.301,0.043,0.457,0.062c0.744,0.09,1.514,0.167,2.305,0.233c0.195,0.016,0.398,0.028,0.596,0.042 - c0.633,0.046,1.28,0.084,1.942,0.114c0.241,0.011,0.481,0.022,0.727,0.031C25.712,18.979,26.59,19,27.5,19s1.788-0.021,2.65-0.05 - c0.245-0.009,0.485-0.02,0.727-0.031c0.662-0.03,1.309-0.068,1.942-0.114c0.198-0.015,0.4-0.026,0.596-0.042 - c0.791-0.065,1.561-0.143,2.305-0.233c0.156-0.019,0.303-0.042,0.457-0.062c0.599-0.078,1.182-0.163,1.75-0.256 - c0.206-0.034,0.411-0.068,0.613-0.104c0.596-0.106,1.176-0.219,1.735-0.341c0.088-0.019,0.182-0.036,0.269-0.055 - c0.634-0.142,1.238-0.297,1.823-0.458c0.163-0.045,0.319-0.092,0.478-0.138c0.441-0.129,0.867-0.262,1.279-0.401 - c0.147-0.05,0.297-0.098,0.44-0.149c0.518-0.184,1.017-0.374,1.485-0.573c0.067-0.028,0.127-0.058,0.193-0.087 - c0.403-0.176,0.786-0.356,1.151-0.542c0.125-0.064,0.247-0.128,0.367-0.193c0.327-0.175,0.638-0.354,0.932-0.538 - c0.076-0.047,0.158-0.093,0.231-0.14c0.351-0.227,0.675-0.459,0.975-0.696c0.075-0.06,0.142-0.12,0.215-0.18 - C50.243,13.509,50.38,13.402,50.5,13.293z M27.5,2c13.555,0,23,3.952,23,7.5s-9.445,7.5-23,7.5s-23-3.952-23-7.5S13.945,2,27.5,2z - M50.5,45.703c-0.014,0.044-0.024,0.089-0.032,0.135C49.901,49.297,40.536,53,27.5,53S5.099,49.297,4.532,45.838 - c-0.008-0.045-0.019-0.089-0.032-0.131v-8.414c0.028,0.026,0.063,0.051,0.092,0.077c0.218,0.192,0.44,0.383,0.69,0.567 - C9.049,40.786,16.582,43,27.5,43c10.872,0,18.386-2.196,22.169-5.028c0.302-0.22,0.574-0.447,0.83-0.678l0.001-0.001V45.703z" /> - </svg> - </div> - </div> - <div class="pure-u-3-5"> - <h4 style="text-align: right; font-size: 1rem"><%:Volumes%></h4> - <h4 style="text-align: right;"> - <%- if self.volumes_total ~= "-" then -%><a href='<%=luci.dispatcher.build_url("admin/docker/volumes")%>'><%- end -%> - <span style="font-size: 2rem; color: #2dce89;"><%=self.volumes_total%></span> - <!-- <span style="font-size: 1rem; color: #8898aa !important;">/20</span> --> - <%- if self.volumes_total ~= "-" then -%></a><%- end -%> - </h4> - </div> - </div> - </div> + <div class="pure-u-1-4"> + <div class="block pure-g"> + <div class="pure-u-2-5"> + <div class="img-con"> + <img src="<%=resource%>/dockerman/containers.svg" /> + </div> + </div> + <div class="pure-u-3-5"> + <h4 style="text-align: right; font-size: 1rem"><%:Containers%></h4> + <h4 style="text-align: right;"> + <%- if self.containers_total ~= "-" then -%><a href='<%=luci.dispatcher.build_url("admin/docker/containers")%>'><%- end -%> + <span style="font-size: 2rem; color: #2dce89;"><%=self.containers_running%></span> + <span style="font-size: 1rem; color: #8898aa !important;">/<%=self.containers_total%></span> + <%- if self.containers_total ~= "-" then -%></a><%- end -%> + </h4> + </div> + </div> + </div> + <div class="pure-u-1-4"> + <div class="block pure-g"> + <div class="pure-u-2-5"> + <div class="img-con"> + <img src="<%=resource%>/dockerman/images.svg" /> + </div> + </div> + <div class="pure-u-3-5"> + <h4 style="text-align: right; font-size: 1rem"><%:Images%></h4> + <h4 style="text-align: right;"> + <%- if self.images_total ~= "-" then -%><a href='<%=luci.dispatcher.build_url("admin/docker/images")%>'><%- end -%> + <span style="font-size: 2rem; color: #2dce89;"><%=self.images_used%></span> + <span style="font-size: 1rem; color: #8898aa !important;">/<%=self.images_total%></span> + <%- if self.images_total ~= "-" then -%></a><%- end -%> + </h4> + </div> + </div> + </div> + <div class="pure-u-1-4"> + <div class="block pure-g"> + <div class="pure-u-2-5"> + <div class="img-con"> + <img src="<%=resource%>/dockerman/networks.svg" /> + </div> + </div> + <div class="pure-u-3-5"> + <h4 style="text-align: right; font-size: 1rem"><%:Networks%></h4> + <h4 style="text-align: right;"> + <%- if self.networks_total ~= "-" then -%><a href='<%=luci.dispatcher.build_url("admin/docker/networks")%>'><%- end -%> + <span style="font-size: 2rem; color: #2dce89;"><%=self.networks_total%></span> + <!-- <span style="font-size: 1rem; color: #8898aa !important;">/20</span> --> + <%- if self.networks_total ~= "-" then -%></a><%- end -%> + </h4> + </div> + </div> + </div> + <div class="pure-u-1-4"> + <div class="block pure-g"> + <div class="pure-u-2-5"> + <div class="img-con"> + <img src="<%=resource%>/dockerman/volumes.svg" /> + </div> + </div> + <div class="pure-u-3-5"> + <h4 style="text-align: right; font-size: 1rem"><%:Volumes%></h4> + <h4 style="text-align: right;"> + <%- if self.volumes_total ~= "-" then -%><a href='<%=luci.dispatcher.build_url("admin/docker/volumes")%>'><%- end -%> + <span style="font-size: 2rem; color: #2dce89;"><%=self.volumes_total%></span> + <!-- <span style="font-size: 1rem; color: #8898aa !important;">/20</span> --> + <%- if self.volumes_total ~= "-" then -%></a><%- end -%> + </h4> + </div> + </div> + </div> </div> diff --git a/applications/luci-app-dockerman/root/etc/config/dockerman b/applications/luci-app-dockerman/root/etc/config/dockerman deleted file mode 100644 index ae7239a788..0000000000 --- a/applications/luci-app-dockerman/root/etc/config/dockerman +++ /dev/null @@ -1,10 +0,0 @@ -config section 'local' - option socket_path '/var/run/docker.sock' - option status_path '/tmp/.docker_action_status' - option debug 'false' - option debug_path '/tmp/.docker_debug' - option remote_endpoint 'false' - option daemon_ea 'true' - option daemon_data_root '/opt/docker' - option daemon_log_level 'warn' - list ac_allowed_interface 'br-lan' diff --git a/applications/luci-app-dockerman/root/etc/init.d/dockerman b/applications/luci-app-dockerman/root/etc/init.d/dockerman deleted file mode 100755 index 601dcf9c7c..0000000000 --- a/applications/luci-app-dockerman/root/etc/init.d/dockerman +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/sh /etc/rc.common - -START=99 -DOCKERD_CONF="/etc/docker/daemon.json" - -init_dockerman_chain(){ - iptables -N DOCKER-MAN >/dev/null 2>&1 - iptables -F DOCKER-MAN >/dev/null 2>&1 - iptables -D DOCKER-USER -j DOCKER-MAN >/dev/null 2>&1 - iptables -I DOCKER-USER -j DOCKER-MAN >/dev/null 2>&1 -} - -add_allowed_interface(){ - iptables -A DOCKER-MAN -i $1 -o docker0 -j RETURN -} - -add_allowed_ip(){ - iptables -A DOCKER-MAN -d $1 -o docker0 -j RETURN -} - -handle_allowed_interface(){ - #config_list_foreach "local" allowed_ip add_allowed_ip - config_list_foreach "local" ac_allowed_interface add_allowed_interface - iptables -A DOCKER-MAN -m conntrack --ctstate ESTABLISHED,RELATED -o docker0 -j RETURN >/dev/null 2>&1 - iptables -A DOCKER-MAN -m conntrack --ctstate NEW,INVALID -o docker0 -j DROP >/dev/null 2>&1 - iptables -A DOCKER-MAN -j RETURN >/dev/null 2>&1 -} - -start(){ - [ ! -x "/etc/init.d/dockerd" ] && return 0 - - config_load dockerman - config_get daemon_ea "local" daemon_ea - - init_dockerman_chain - if [ -n "$daemon_ea" ]; then - handle_allowed_interface - lua /usr/share/dockerman/dockerd-config.lua "$DOCKERD_CONF" && /etc/init.d/dockerd restart && sleep 5 || { - # 1 running, 0 stopped - STATE=$([ -n "$(ps |grep /usr/bin/dockerd | grep -v grep)" ] && echo 1 || echo 0) - [ "$STATE" == "0" ] && /etc/init.d/dockerd start && sleep 5 - } - lua /usr/share/dockerman/dockerd-ac.lua - else - /etc/init.d/dockerd stop - fi -} diff --git a/applications/luci-app-dockerman/root/etc/uci-defaults/luci-app-dockerman b/applications/luci-app-dockerman/root/etc/uci-defaults/luci-app-dockerman deleted file mode 100755 index c5a54178e5..0000000000 --- a/applications/luci-app-dockerman/root/etc/uci-defaults/luci-app-dockerman +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh - -uci -q batch <<-EOF >/dev/null - set uhttpd.main.script_timeout="360" - commit uhttpd - delete ucitrack.@dockerman[-1] - add ucitrack dockerman - set ucitrack.@dockerman[-1].exec='/etc/init.d/dockerman start' - commit ucitrack -EOF -[ -x "$(which dockerd)" ] && chmod +x /etc/init.d/dockerman && /etc/init.d/dockerd disable && /etc/init.d/dockerman enable >/dev/null 2>&1 -sed -i 's/self:cfgvalue(section) or {}/self:cfgvalue(section) or self.default or {}/' /usr/lib/lua/luci/view/cbi/dynlist.htm -/etc/init.d/uhttpd restart >/dev/null 2>&1 -rm -fr /tmp/luci-indexcache /tmp/luci-modulecache >/dev/null 2>&1 -exit 0 diff --git a/applications/luci-app-dockerman/root/usr/share/dockerman/dockerd-ac.lua b/applications/luci-app-dockerman/root/usr/share/dockerman/dockerd-ac.lua deleted file mode 100644 index e8a2c0b7eb..0000000000 --- a/applications/luci-app-dockerman/root/usr/share/dockerman/dockerd-ac.lua +++ /dev/null @@ -1,20 +0,0 @@ -require "luci.util" -docker = require "luci.docker" -uci = (require "luci.model.uci").cursor() -dk = docker.new({socket_path = "/var/run/docker.sock"}) - -if dk:_ping().code ~= 200 then return end -containers_list = dk.containers:list({query = {all=true}}).body -allowed_container = uci:get("dockerman", "local", "ac_allowed_container") - -if not allowed_container or next(allowed_container)==nil then return end -allowed_ip = {} -for i, v in ipairs(containers_list) do - for ii, vv in ipairs(allowed_container) do - if v.Id:sub(1,12) == vv and v.NetworkSettings and v.NetworkSettings.Networks and v.NetworkSettings.Networks.bridge and v.NetworkSettings.Networks.bridge.IPAddress then - print(v.NetworkSettings.Networks.bridge.IPAddress) - luci.util.exec("iptables -I DOCKER-MAN -d "..v.NetworkSettings.Networks.bridge.IPAddress.." -o docker0 -j RETURN") - table.remove(allowed_container, ii) - end - end -end diff --git a/applications/luci-app-dockerman/root/usr/share/dockerman/dockerd-config.lua b/applications/luci-app-dockerman/root/usr/share/dockerman/dockerd-config.lua deleted file mode 100644 index 179868869b..0000000000 --- a/applications/luci-app-dockerman/root/usr/share/dockerman/dockerd-config.lua +++ /dev/null @@ -1,52 +0,0 @@ -require "luci.util" -fs = require "nixio.fs" -uci = (require "luci.model.uci").cursor() - -raw_file_dir = arg[1] - -raw_json_str = fs.readfile(raw_file_dir) or "[]" -raw_json = luci.jsonc.parse(raw_json_str) or {} - -new_json = {} -new_json["data-root"] = uci:get("dockerman", "local", "daemon_data_root") -new_json["hosts"] = uci:get("dockerman", "local", "daemon_hosts") or {} -new_json["registry-mirrors"] = uci:get("dockerman", "local", "daemon_registry_mirrors") or {} -new_json["log-level"] = uci:get("dockerman", "local", "daemon_log_level") - -function comp(raw, new) - for k, v in pairs(new) do - if type(v) == "table" and raw[k] then - if #v == #raw[k] then - comp(raw[k], v) - else - changed = true - raw[k] = v - end - elseif raw[k] ~= v then - changed = true - raw[k] = v - end - end - for k, v in ipairs(new) do - if type(v) == "table" and raw[k] then - if #v == #raw[k] then - comp(raw[k], v) - else - changed = true - raw[k] = v - end - elseif raw[k] ~= v then - changed = true - raw[k] = v - end - end -end -comp(raw_json, new_json) -if changed then - if next(raw_json["registry-mirrors"]) == nil then raw_json["registry-mirrors"] = nil end - if next(raw_json["hosts"]) == nil then raw_json["hosts"] = nil end - fs.writefile(raw_file_dir, luci.jsonc.stringify(raw_json, true):gsub("\\", "")) - os.exit(0) -else - os.exit(1) -end diff --git a/applications/luci-app-dockerman/root/usr/share/rpcd/acl.d/luci-app-dockerman.json b/applications/luci-app-dockerman/root/usr/share/rpcd/acl.d/luci-app-dockerman.json index ba1a10c609..353ccaa16c 100644 --- a/applications/luci-app-dockerman/root/usr/share/rpcd/acl.d/luci-app-dockerman.json +++ b/applications/luci-app-dockerman/root/usr/share/rpcd/acl.d/luci-app-dockerman.json @@ -2,10 +2,10 @@ "luci-app-dockerman": { "description": "Grant UCI access for luci-app-dockerman", "read": { - "uci": [ "dockerman" ] + "uci": [ "dockerd" ] }, "write": { - "uci": [ "dockerman" ] + "uci": [ "dockerd" ] } } } |