Compare commits
698 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 83abb310b4 | |||
| 3d98311d9f | |||
| 611bd68739 | |||
| 25eeb88a8f | |||
| d44e38b462 | |||
| 8948c972ee | |||
| 3e770defae | |||
| ae4656e699 | |||
| f6a9e690bf | |||
| 7f86a21317 | |||
| fb39c7c26b | |||
| 13c3b9cbcb | |||
| 602dac8773 | |||
| c6e76160ad | |||
| a53b909fcd | |||
| 22909c849e | |||
| 440866d26c | |||
| 25b936c369 | |||
| 8f66cfa316 | |||
| 8912b6bf3b | |||
| a799351926 | |||
| 9df01035da | |||
| 1ad96ee09f | |||
| 1b4bb94ac4 | |||
| f38222c0cc | |||
| c9b1468c3b | |||
| 6800902b43 | |||
| e5773e11c6 | |||
| 0c7694894b | |||
| ce9aa2f2b2 | |||
| 7547f3c8a3 | |||
| 31dff87903 | |||
| fd72c21e0e | |||
| 310bf5bd36 | |||
| a170e44689 | |||
| 70e70446bb | |||
| 45d9cfa0c5 | |||
| 93fd269329 | |||
| 6717a0b68c | |||
| 533338bdeb | |||
| b6ebf2aba8 | |||
| ed4fcb63f7 | |||
| 4b4cb07fb6 | |||
| fc446e0b81 | |||
| f612a7dd85 | |||
| 0472e5c1e1 | |||
| 6a6c930f5b | |||
| 35ca3e5d9b | |||
| a9ab01731f | |||
| cd80e6df29 | |||
| 4dea4f3b89 | |||
| 1b30a3be2b | |||
| f7f103725b | |||
| 77a192a7b5 | |||
| cb48517a78 | |||
| e9394dc22d | |||
| 46743c7918 | |||
| b288eaddb3 | |||
| 2665aba555 | |||
| 9b6bcb30d9 | |||
| ac9ba13c19 | |||
| 864b8795aa | |||
| cc1575f944 | |||
| b408d0e87d | |||
| 69b81af93e | |||
| cf558ff6d1 | |||
| 5af7bd6a01 | |||
| 73f4f29110 | |||
| ccd949d8ca | |||
| e1bc99e4fe | |||
| 02194ee3c6 | |||
| 85f2b74cfd | |||
| 152cdf1c05 | |||
| 17dd1c19df | |||
| 8e49a3d047 | |||
| cccf77229d | |||
| dafdc74605 | |||
| c753b5dfeb | |||
| 82373e3d50 | |||
| 76b8abcd8e | |||
| dbb5408906 | |||
| 29b49f9343 | |||
| f8e4700a11 | |||
| 18c9d49a32 | |||
| 4c5c00c640 | |||
| 0be44133d4 | |||
| d41dcb784b | |||
| 659d5aabd1 | |||
| b4343a28b7 | |||
| 0e693e0284 | |||
| fc880f9b23 | |||
| cead24b0f7 | |||
| 8489f22fe2 | |||
| fc6f804865 | |||
| d1d89116f1 | |||
| e1f64141a2 | |||
| 586058f079 | |||
| 4e926b292f | |||
| 47358e3104 | |||
| 3640c63289 | |||
| 51fa4f7fe3 | |||
| c3f7d1026f | |||
| 80f1387877 | |||
| 0674fa43ff | |||
| a17f1e875c | |||
| 9e1f1b50e0 | |||
| 0715032598 | |||
| dfc2ef3004 | |||
| 62f8343879 | |||
| f803bb4b3d | |||
| 20a65981bd | |||
| 09b450d610 | |||
| c194b9f5f1 | |||
| 98a6a2bcab | |||
| 1c699d8d3f | |||
| a054c73e22 | |||
| b580ad24e2 | |||
| f8a3b1e164 | |||
| bf2b8879b7 | |||
| caecd75a2a | |||
| 2959c104b3 | |||
| 50796e481d | |||
| f64f8b03cb | |||
| 7b85756c64 | |||
| d6a327fbc5 | |||
| f24753812a | |||
| 08b6cfb082 | |||
| 12a7a15daa | |||
| 7c6d77734a | |||
| b0cfceb211 | |||
| 0563a9235a | |||
| eb6d53d2f5 | |||
| 95d9e47353 | |||
| e1142e99f2 | |||
| a091fe3ed6 | |||
| f37f56ac15 | |||
| 2f4a7483ea | |||
| 464f9d34d6 | |||
| 926c75d0b5 | |||
| c8f57133a4 | |||
| cae62ce543 | |||
| 7bbb532b4b | |||
| 6d10f4c19a | |||
| cde6496172 | |||
| 8562b22823 | |||
| dd68cdd802 | |||
| 41cf580bb1 | |||
| 00baec1746 | |||
| f9025652df | |||
| ee0172dfe4 | |||
| 69559aa101 | |||
| 36fd317eb2 | |||
| e8ce3b64ed | |||
| 6ee27daac1 | |||
| cdc6c2d578 | |||
| 855ef4f1aa | |||
| 5878fcc086 | |||
| 1ac36a95aa | |||
| 15b4c49621 | |||
| f57f773189 | |||
| e7b4e4e105 | |||
| 1d134c1a94 | |||
| 329a910b86 | |||
| 4309727354 | |||
| ef3319a49d | |||
| d8e4d3c9c8 | |||
| faa6b1e925 | |||
| 85c6bb9809 | |||
| 5404dddcea | |||
| 4692fdbb8f | |||
| 1875a47495 | |||
| 9c9f390350 | |||
| 1b2b16998f | |||
| 7a17b2a585 | |||
| f7766b49aa | |||
| b2f920a868 | |||
| 8ceb969445 | |||
| 0db484c8f6 | |||
| 5c481fbf6e | |||
| 8e6e9301ce | |||
| 664ff063a1 | |||
| d9fd6f9a96 | |||
| b28ff50126 | |||
| 3fe2d77b70 | |||
| ab49498fc3 | |||
| 46fdd70114 | |||
| 817e763416 | |||
| 0abab5305f | |||
| 29d72b84c0 | |||
| f53fcdf10e | |||
| 8e665105b1 | |||
| e6e855a7cf | |||
| 6fca1d164c | |||
| 2062add05f | |||
| 9b76eba37e | |||
| 03714d4809 | |||
| 6b4e6bcebf | |||
| 2d6f8153fa | |||
| 5c10f4adf0 | |||
| 4daa0d2cee | |||
| cd0f350c02 | |||
| ac554bf663 | |||
| b363709c11 | |||
| 410b579e87 | |||
| 7b7c0bba58 | |||
| 0cef971832 | |||
| 5c9a95df32 | |||
| 3b455d6137 | |||
| b2a0e5754b | |||
| 63d9bb626a | |||
| 372da5eaf5 | |||
| cf17fc7774 | |||
| 67509453d3 | |||
| d31f256020 | |||
| d67f47927c | |||
| 85bb5870a9 | |||
| 4a9b205a15 | |||
| f3b346cbb3 | |||
| ff4ce0ee14 | |||
| 918924796f | |||
| feb6685359 | |||
| 0d59ea1e94 | |||
| 38edd94282 | |||
| 8021061bd8 | |||
| bc9509bc8a | |||
| e3b4910b66 | |||
| d056f19d07 | |||
| 834007728c | |||
| a609a4126c | |||
| bcd1a2308b | |||
| 694bad434c | |||
| 01f625824a | |||
| 9ccfb70104 | |||
| 201a20ac02 | |||
| f3322e94c8 | |||
| 3a73ea3f54 | |||
| 9625629fc7 | |||
| 46a6fde813 | |||
| fd1f09a66c | |||
| 752ed7915b | |||
| d3064e40b3 | |||
| 39f9324616 | |||
| a822b8e782 | |||
| a6922f94ee | |||
| b4d57bf4c4 | |||
| 0f2e493c9a | |||
| 4ef2d8940c | |||
| 96099a1e97 | |||
| 8507ce2a87 | |||
| 0e87f29de9 | |||
| dd74e5a809 | |||
| 12c1abed98 | |||
| e2ef95af7d | |||
| d711ff454e | |||
| c667d20dfc | |||
| 6efee2072d | |||
| 7a5bbf7a2e | |||
| 14cb3645a3 | |||
| f4f512fedd | |||
| 1983925dcf | |||
| 22c98d3fa2 | |||
| 01ae5bb39c | |||
| 11faf3f16d | |||
| d3df6c711a | |||
| ed6e781679 | |||
| baf1c1638d | |||
| 825000bc34 | |||
| c1ebcbcda2 | |||
| 41a3a9e402 | |||
| 1d4ac4b084 | |||
| 71b357e4f2 | |||
| 339c9019b9 | |||
| dd93f808c8 | |||
| 4098025c11 | |||
| bef0a50bc1 | |||
| 71ada9d6f8 | |||
| 33c022fcec | |||
| 273228fafa | |||
| aa98702908 | |||
| 5197649081 | |||
| 066f64d34a | |||
| afa4c7c3ef | |||
| 38537c7df2 | |||
| eed9ab0464 | |||
| 0b9f0d14a1 | |||
| ffbee70e04 | |||
| 2c3a2241e7 | |||
| 1c47365445 | |||
| 81eb7c0301 | |||
| 4a926d19a7 | |||
| 05dc52a206 | |||
| c91d3791f9 | |||
| df8153859c | |||
| 2f43709f85 | |||
| 61d67a061c | |||
| 1b85302161 | |||
| 6512d9978e | |||
| e216201901 | |||
| 7abcededf2 | |||
| 398421b9f5 | |||
| b8f0d9352f | |||
| f234c35020 | |||
| 3bcf7b7593 | |||
| 0c042a622a | |||
| c57a358c9d | |||
| 380d6c6435 | |||
| 716316f711 | |||
| 98e79b4b50 | |||
| e2216a0936 | |||
| e8d9d710d0 | |||
| a8ab02a73a | |||
| cf022de4d5 | |||
| 3995e21c5b | |||
| 2f7fb78692 | |||
| cf2fb30ac7 | |||
| 51d1e6f75b | |||
| 2f373684f5 | |||
| 5741a53d46 | |||
| dd0698d14c | |||
| 3e67bfcc88 | |||
| ea579f5b69 | |||
| daf6f3a5c0 | |||
| d44e9d6da9 | |||
| 9c2cfb5cb6 | |||
| 91a0ade908 | |||
| 5eb4002bf7 | |||
| 85a5c358d8 | |||
| 33c771bb3e | |||
| 286a8924fd | |||
| efca29b00e | |||
| 9161016962 | |||
| 5ff30777e1 | |||
| 754c0770d6 | |||
| 1105508453 | |||
| 1ba497950c | |||
| bf3c93a8cc | |||
| eb1bc67db1 | |||
| 8408dfaa6c | |||
| d192044915 | |||
| d9bab6b3bd | |||
| 4503491a0a | |||
| e6965ca066 | |||
| d9674f7524 | |||
| f024deb1f8 | |||
| 79601d27f2 | |||
| 226a69fe15 | |||
| 9bb799462e | |||
| 8bffa78f7f | |||
| 4b302173ae | |||
| 9db2205db3 | |||
| 6914fe436c | |||
| 807cc3c28d | |||
| f3c6c55719 | |||
| a773a6dce6 | |||
| 62dcee3b14 | |||
| 490159ea89 | |||
| 3e8cb8c937 | |||
| dd6ecf1193 | |||
| 90e1803798 | |||
| a47ce7ab22 | |||
| 0b3282ac9f | |||
| 79b98b5c25 | |||
| 6a57f2c1f0 | |||
| 0fa2477cb0 | |||
| 7ffb337cd7 | |||
| a9dd2793f9 | |||
| 9e2d0ac50b | |||
| 30dc11a931 | |||
| a15dc5fed5 | |||
| f7c20b97a1 | |||
| e9c45102b0 | |||
| 8871eb8e1e | |||
| 0f32efb825 | |||
| 63f1b4fdf4 | |||
| 31f2db6880 | |||
| 2c9b406f6c | |||
| 12cd2da265 | |||
| 485c0ea8bf | |||
| 49ba07d1d6 | |||
| 35e541e0b1 | |||
| 3977d6b7bd | |||
| 0905398459 | |||
| f41faf96b7 | |||
| 36639b65a9 | |||
| 7fc598b73f | |||
| 21d41ad7fd | |||
| fe56220d1a | |||
| 5782ec3ada | |||
| 8839c5105a | |||
| e293f079f5 | |||
| 6c2d5e3d05 | |||
| 5999a23d3e | |||
| e51be73ac7 | |||
| 4b5b363c6c | |||
| f2b0c08e34 | |||
| 83b364891d | |||
| 87af2dbc43 | |||
| 885bac330b | |||
| e3ab478c70 | |||
| ecaccefd2e | |||
| ece559afe2 | |||
| d3aaf50a40 | |||
| 43ce028840 | |||
| 32c3a558e9 | |||
| 41f9ab1c69 | |||
| 8876e0a80a | |||
| c21ac80268 | |||
| 0e2cd1a64d | |||
| 6274727b71 | |||
| 95567c68bf | |||
| c189337b6e | |||
| 6fc81dc162 | |||
| 7d50361ca9 | |||
| 19c51f3f3c | |||
| c3bbadacbf | |||
| 2208992e6a | |||
| bbba8e432a | |||
| 39331b6b4e | |||
| 6e748cb1cf | |||
| 2005d656e6 | |||
| 0f26ec8095 | |||
| 8d2347bc7b | |||
| 0ff359ca0e | |||
| f8e13fb00e | |||
| 686a610fc3 | |||
| 7fc1685b7a | |||
| b89609dc2e | |||
| fa50312220 | |||
| fee3f99a6e | |||
| ea406754a6 | |||
| d02cb963d5 | |||
| 334cdb5d64 | |||
| a681af6953 | |||
| 5c3639c1b7 | |||
| 903574db90 | |||
| f463d3ce42 | |||
| 17eeec6895 | |||
| 070d3610ff | |||
| f22862aa28 | |||
| 18be3bc95a | |||
| 2f520ed92f | |||
| c03ce0f74a | |||
| 0320a77dc0 | |||
| 3349153058 | |||
| 5e86352464 | |||
| 18d125ef1c | |||
| 31be826f51 | |||
| eb372d53df | |||
| 1788c58d5c | |||
| 17e49a9ed2 | |||
| 0aabc1d8d9 | |||
| 55f6828750 | |||
| 6303f26330 | |||
| b59bac670a | |||
| 84acc820c7 | |||
| 92858c7db2 | |||
| fdaa129a5b | |||
| df59967f59 | |||
| 73982c8cb6 | |||
| 6199aa0707 | |||
| a5921b5743 | |||
| f0538dbb5c | |||
| ff91ecb177 | |||
| a6357502c1 | |||
| fa8ea41cd9 | |||
| 3ac6790ca2 | |||
| 268b96058f | |||
| 0d031c432b | |||
| 6e8f3224c5 | |||
| 9c53e9f4c3 | |||
| 2d5e988a6d | |||
| 3cfb23750a | |||
| 57f75b1d9b | |||
| 4560cbc20c | |||
| e213172431 | |||
| f5f007e183 | |||
| 09463265ce | |||
| 8f4cf2a7d0 | |||
| 30183c4a9a | |||
| d1df5e0ae1 | |||
| f1bc9343a1 | |||
| e53a9f6cab | |||
| 97f4989945 | |||
| ed676667d0 | |||
| 1e5e213d24 | |||
| a8105ec068 | |||
| c6d2967b84 | |||
| bb9be02228 | |||
| 64f37bbf5b | |||
| 0fc96fa25c | |||
| 2d8c6f8288 | |||
| b94211bd66 | |||
| 6494b77d41 | |||
| 63a7ca1af0 | |||
| 95f544657a | |||
| 740a919e25 | |||
| 402c92beda | |||
| 6a9013b97c | |||
| 73b50aea2d | |||
| 315e66858c | |||
| 4f902d42b2 | |||
| 61a18ed48f | |||
| b5a6dd1395 | |||
| 83e6e1060e | |||
| ddeb8242d8 | |||
| a08052f640 | |||
| 5ecba587f7 | |||
| db3da97a50 | |||
| 5885ffc8ae | |||
| 9fb1c89f81 | |||
| 1e6afac3bd | |||
| 64083b9227 | |||
| 5fdd768903 | |||
| f72163f856 | |||
| 428629f577 | |||
| 1edd74dda2 | |||
| 740ecf530c | |||
| 10d2ef5449 | |||
| 8ea571c7f7 | |||
| ca3022d545 | |||
| 9417614a8e | |||
| 70d52bbc4c | |||
| 6aa2fc95c0 | |||
| cfbab22237 | |||
| be0e06c0aa | |||
| 1c941fd787 | |||
| 669783f875 | |||
| 27e474b3d2 | |||
| 1e53316241 | |||
| ca5ca8332b | |||
| aa579bbc20 | |||
| e375d822da | |||
| 4bc2eb9a4d | |||
| fc783f8407 | |||
| 79e0c52fc2 | |||
| ef22330563 | |||
| e1adbf83d8 | |||
| 1148f95292 | |||
| f7430ec09c | |||
| b8903d842c | |||
| 32f097b4d6 | |||
| 559ad38b8c | |||
| 90417d2dd6 | |||
| e66fb4bd7b | |||
| 018c90dae7 | |||
| a5066e8f76 | |||
| 0a3448c8b6 | |||
| c4964232eb | |||
| ef3c807b4a | |||
| 113b93346b | |||
| a955676986 | |||
| 725df557b5 | |||
| 250ac644d6 | |||
| 490056eee3 | |||
| 797d749322 | |||
| 79888bfff7 | |||
| 6ca57e81f1 | |||
| 010f61e91f | |||
| 9216981262 | |||
| b89a1cd482 | |||
| 7d37dea449 | |||
| df2ef64d20 | |||
| 7a549096de | |||
| a1ff351f21 | |||
| 03430d0db8 | |||
| 6f73d597e0 | |||
| e273a40345 | |||
| a84548d7ea | |||
| 19e01ceb19 | |||
| 1e6d67b16d | |||
| 6533927237 | |||
| da9ae574df | |||
| 16e4a7c200 | |||
| ee7e70c992 | |||
| 54a9f59a13 | |||
| e6fd664331 | |||
| b247ec9352 | |||
| 43befab8ef | |||
| eae0281c64 | |||
| 8bd78fbdfb | |||
| 66459a4ce0 | |||
| 182109f0de | |||
| 9998e9ea19 | |||
| ba2ba328da | |||
| 67b8080144 | |||
| 239a34ca97 | |||
| 81d8263ae2 | |||
| b4e4846e9f | |||
| 92e49eab5a | |||
| c4a7b950a0 | |||
| 1ce339abeb | |||
| a1f922315b | |||
| 3e284162d7 | |||
| ff99392102 | |||
| 9358ee011b | |||
| 37cbcae6ba | |||
| ae64b7fac8 | |||
| d936ed90ae | |||
| 7cc77eba45 | |||
| 07d232c7b4 | |||
| de67bca211 | |||
| 5c4dbc966f | |||
| bca1385683 | |||
| f655592ff5 | |||
| cbe87cb2ed | |||
| 0513b3ed07 | |||
| 7f92165c79 | |||
| 780ccc26f7 | |||
| 339425fd52 | |||
| fa6e9540a8 | |||
| 9605fde04d | |||
| 200d327737 | |||
| e1b0985b5b | |||
| c8dfc4cda4 | |||
| 7922a54c9a | |||
| bda6bcd5be | |||
| 7e3e24b394 | |||
| bb0466f4ce | |||
| 8797952409 | |||
| c33d1b8ee6 | |||
| 1f67c18222 | |||
| a4691611a7 | |||
| 7c7eb1475d | |||
| 0bce3d6d63 | |||
| 804d91ef61 | |||
| 229600ce9b | |||
| 43098df9d8 | |||
| 6908a0dcd4 | |||
| cae5761f16 | |||
| 83074ed57e | |||
| 64a7e6992e | |||
| 61af764f8a | |||
| b4172ad3c8 | |||
| 4917038f55 | |||
| b0f3f94163 | |||
| b7c169d71d | |||
| 3939c75345 | |||
| e55926e8cf | |||
| f82f535509 | |||
| 51e337228e | |||
| 241c56e6d7 | |||
| 895471afa1 | |||
| 85e2d886bc | |||
| 07aa02f196 | |||
| 8acf4d112a | |||
| e6abfb3b67 | |||
| 040af08473 | |||
| e3de237eb8 | |||
| 11785529fc | |||
| 32201a18ab | |||
| 421cccb1d7 | |||
| 400e9309f1 | |||
| a4afb312d4 | |||
| afe874f15a | |||
| b291dbe9c5 | |||
| bc8f34bfe7 | |||
| 2dc7ecc59b | |||
| 600551feb9 | |||
| 7398d737b5 | |||
| bf62ba57cf | |||
| 511a8cbe04 | |||
| 790ad68d4d | |||
| e79d2381fc | |||
| a3b4199e9b | |||
| 3aa0574c6b | |||
| 3118195e92 | |||
| 5716f1c199 | |||
| 2020afd200 | |||
| 7431acb2c4 | |||
| 2f9975c76c | |||
| d9bd73f4e8 | |||
| 8484d1c0ca | |||
| 7270ca4157 | |||
| b67c8970c9 | |||
| 418921de89 | |||
| ec4260d260 | |||
| c39d3db7a0 | |||
| 2da0cfc904 | |||
| 9dd0e3dc44 | |||
| 4722c94653 | |||
| 45c928e2f5 | |||
| 07506358ff | |||
| 975eb31973 | |||
| a3c509fd23 | |||
| 63bb78cfc6 | |||
| f2fd8b0265 | |||
| a8052889fe | |||
| bccf549463 | |||
| 73d1a46f3e | |||
| 95c16f51cb | |||
| 810dcbf34b | |||
| cb77586508 | |||
| e2498edb45 | |||
| 0b7dfab17a | |||
| 674fdc4304 | |||
| 10feb1af3f | |||
| d2b924cca8 | |||
| 13390d0c95 |
@@ -1,13 +1,33 @@
|
||||
## Expected behaviour
|
||||
<!--- Provide a general summary of the issue in the Title above -->
|
||||
|
||||
## Actual behaviour
|
||||
## Expected Behavior
|
||||
<!--- If you're describing a bug, tell us what should happen -->
|
||||
<!--- If you're suggesting a change/improvement, tell us how it should work -->
|
||||
|
||||
## Steps to reproduce the behaviour
|
||||
## Current Behavior
|
||||
<!--- If describing a bug, tell us what happens instead of the expected behavior -->
|
||||
<!--- If suggesting a change/improvement, explain the difference from current behavior -->
|
||||
|
||||
## Minio version
|
||||
- (paste output of `minio version`)
|
||||
|
||||
## System information
|
||||
## Possible Solution
|
||||
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
|
||||
<!--- or ideas how to implement the addition or change -->
|
||||
|
||||
## Steps to Reproduce (for bugs)
|
||||
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
|
||||
<!--- reproduce this bug. Include code to reproduce, if relevant -->
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
4.
|
||||
|
||||
## Context
|
||||
<!--- How has this issue affected you? What are you trying to accomplish? -->
|
||||
<!--- Providing context helps us come up with a solution that is most useful in the real world -->
|
||||
|
||||
## Your Environment
|
||||
<!--- Include as many relevant details about the environment you experienced the bug in -->
|
||||
* Version used:
|
||||
* Environment name and version (e.g. nginx 1.9.1):
|
||||
* Server type and version:
|
||||
* Operating System and version:
|
||||
* Link to your project:
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<!--- Provide a general summary of your changes in the Title above -->
|
||||
|
||||
## Description
|
||||
<!--- Describe your changes in detail -->
|
||||
|
||||
## Motivation and Context
|
||||
<!--- Why is this change required? What problem does it solve? -->
|
||||
<!--- If it fixes an open issue, please link to the issue here. -->
|
||||
|
||||
## How Has This Been Tested?
|
||||
<!--- Please describe in detail how you tested your changes. -->
|
||||
<!--- Include details of your testing environment, and the tests you ran to -->
|
||||
<!--- see how your change affects other areas of the code, etc. -->
|
||||
|
||||
## Types of changes
|
||||
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
|
||||
|
||||
## Checklist:
|
||||
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
|
||||
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
|
||||
- [ ] My code follows the code style of this project.
|
||||
- [ ] My change requires a change to the documentation.
|
||||
- [ ] I have updated the documentation accordingly.
|
||||
- [ ] I have added tests to cover my changes.
|
||||
- [ ] All new and existing tests passed.
|
||||
@@ -1,23 +1,24 @@
|
||||
go_import_path: github.com/minio/minio
|
||||
sudo: required
|
||||
|
||||
dist: trusty
|
||||
|
||||
language: go
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
osx_image: xcode7.2
|
||||
|
||||
env:
|
||||
- ARCH=x86_64
|
||||
- ARCH=i686
|
||||
|
||||
script:
|
||||
- make test GOFLAGS="-race"
|
||||
- make
|
||||
- make test GOFLAGS="-timeout 20m -race -v"
|
||||
- make coverage
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
go:
|
||||
- 1.6.2
|
||||
- 1.7.4
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
### Install Golang
|
||||
|
||||
If you do not have a working Golang environment setup please follow [Golang Installation Guide](./INSTALLGO.md).
|
||||
If you do not have a working Golang environment setup please follow [Golang Installation Guide](https://docs.minio.io/docs/how-to-install-golang).
|
||||
|
||||
### Setup your Minio Github Repository
|
||||
Fork [Minio upstream](https://github.com/minio/minio/fork) source repository to your own personal repository. Copy the URL for minio from your personal github repo (you will need it for the `git clone` command below).
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
FROM golang:1.6
|
||||
FROM golang:1.7-alpine
|
||||
|
||||
RUN mkdir -p /go/src/app
|
||||
WORKDIR /go/src/app
|
||||
|
||||
COPY . /go/src/app
|
||||
RUN go-wrapper download
|
||||
RUN go-wrapper install
|
||||
|
||||
ENV ALLOW_CONTAINER_ROOT=1
|
||||
RUN mkdir -p /export/docker && cp /go/src/app/docs/Docker.md /export/docker/
|
||||
RUN \
|
||||
apk add --no-cache git && \
|
||||
go-wrapper download && \
|
||||
go-wrapper install -ldflags "-X github.com/minio/minio/cmd.Version=2017-02-16T01:47:30Z -X github.com/minio/minio/cmd.ReleaseTag=RELEASE.2017-02-16T01-47-30Z -X github.com/minio/minio/cmd.CommitID=3d98311d9f4ceb78dba996dcdc0751253241e697" && \
|
||||
mkdir -p /export/docker && \
|
||||
rm -rf /go/pkg /go/src && \
|
||||
apk del git
|
||||
|
||||
EXPOSE 9000
|
||||
ENTRYPOINT ["go-wrapper", "run", "server"]
|
||||
CMD ["/export"]
|
||||
ENTRYPOINT ["minio"]
|
||||
VOLUME ["/export"]
|
||||
|
||||
@@ -18,7 +18,7 @@ $
|
||||
|
||||
- Run `go get foo/bar`
|
||||
- Edit your code to import foo/bar
|
||||
- Run `govendor add foo/bar` from top-level folder
|
||||
- Run `govendor add foo/bar` from top-level directory
|
||||
|
||||
#### Remove dependencies
|
||||
|
||||
|
||||
@@ -61,73 +61,75 @@ checks:
|
||||
@(env bash $(PWD)/buildscripts/checkgopath.sh)
|
||||
|
||||
getdeps: checks
|
||||
@go get -u github.com/golang/lint/golint && echo "Installed golint:"
|
||||
@go get -u github.com/fzipp/gocyclo && echo "Installed gocyclo:"
|
||||
@go get -u github.com/remyoudompheng/go-misc/deadcode && echo "Installed deadcode:"
|
||||
@go get -u github.com/client9/misspell/cmd/misspell && echo "Installed misspell:"
|
||||
@go get -u github.com/gordonklaus/ineffassign && echo "Installed ineffassign:"
|
||||
@echo "Installing golint:" && go get -u github.com/golang/lint/golint
|
||||
@echo "Installing gocyclo:" && go get -u github.com/fzipp/gocyclo
|
||||
@echo "Installing deadcode:" && go get -u github.com/remyoudompheng/go-misc/deadcode
|
||||
@echo "Installing misspell:" && go get -u github.com/client9/misspell/cmd/misspell
|
||||
@echo "Installing ineffassign:" && go get -u github.com/gordonklaus/ineffassign
|
||||
|
||||
verifiers: vet fmt lint cyclo spelling
|
||||
|
||||
vet:
|
||||
@echo "Running $@:"
|
||||
@GO15VENDOREXPERIMENT=1 go tool vet -all *.go
|
||||
@GO15VENDOREXPERIMENT=1 go tool vet -all ./pkg
|
||||
@GO15VENDOREXPERIMENT=1 go tool vet -shadow=true *.go
|
||||
@GO15VENDOREXPERIMENT=1 go tool vet -shadow=true ./pkg
|
||||
@go tool vet -all ./cmd
|
||||
@go tool vet -all ./pkg
|
||||
@go tool vet -shadow=true ./cmd
|
||||
@go tool vet -shadow=true ./pkg
|
||||
|
||||
fmt:
|
||||
@echo "Running $@:"
|
||||
@GO15VENDOREXPERIMENT=1 gofmt -s -l *.go
|
||||
@GO15VENDOREXPERIMENT=1 gofmt -s -l pkg
|
||||
@gofmt -s -l cmd
|
||||
@gofmt -s -l pkg
|
||||
|
||||
lint:
|
||||
@echo "Running $@:"
|
||||
@GO15VENDOREXPERIMENT=1 ${GOPATH}/bin/golint *.go
|
||||
@GO15VENDOREXPERIMENT=1 ${GOPATH}/bin/golint github.com/minio/minio/pkg...
|
||||
@${GOPATH}/bin/golint -set_exit_status github.com/minio/minio/cmd...
|
||||
@${GOPATH}/bin/golint -set_exit_status github.com/minio/minio/pkg...
|
||||
|
||||
ineffassign:
|
||||
@echo "Running $@:"
|
||||
@GO15VENDOREXPERIMENT=1 ${GOPATH}/bin/ineffassign .
|
||||
@${GOPATH}/bin/ineffassign .
|
||||
|
||||
cyclo:
|
||||
@echo "Running $@:"
|
||||
@GO15VENDOREXPERIMENT=1 ${GOPATH}/bin/gocyclo -over 65 *.go
|
||||
@GO15VENDOREXPERIMENT=1 ${GOPATH}/bin/gocyclo -over 65 pkg
|
||||
@${GOPATH}/bin/gocyclo -over 100 cmd
|
||||
@${GOPATH}/bin/gocyclo -over 100 pkg
|
||||
|
||||
build: getdeps verifiers $(UI_ASSETS)
|
||||
|
||||
deadcode:
|
||||
@GO15VENDOREXPERIMENT=1 ${GOPATH}/bin/deadcode
|
||||
@${GOPATH}/bin/deadcode
|
||||
|
||||
spelling:
|
||||
@GO15VENDOREXPERIMENT=1 ${GOPATH}/bin/misspell -error *.go
|
||||
@GO15VENDOREXPERIMENT=1 ${GOPATH}/bin/misspell -error pkg/**/*
|
||||
@${GOPATH}/bin/misspell -error `find cmd/`
|
||||
@${GOPATH}/bin/misspell -error `find pkg/`
|
||||
@${GOPATH}/bin/misspell -error `find docs/`
|
||||
|
||||
test: build
|
||||
@echo "Running all minio testing:"
|
||||
@GO15VENDOREXPERIMENT=1 go test $(GOFLAGS) .
|
||||
@GO15VENDOREXPERIMENT=1 go test $(GOFLAGS) github.com/minio/minio/pkg...
|
||||
@go test $(GOFLAGS) .
|
||||
@go test $(GOFLAGS) github.com/minio/minio/cmd...
|
||||
@go test $(GOFLAGS) github.com/minio/minio/pkg...
|
||||
|
||||
coverage: build
|
||||
@echo "Running all coverage for minio:"
|
||||
@GO15VENDOREXPERIMENT=1 ./buildscripts/go-coverage.sh
|
||||
@./buildscripts/go-coverage.sh
|
||||
|
||||
gomake-all: build
|
||||
@echo "Installing minio:"
|
||||
@GO15VENDOREXPERIMENT=1 go build --ldflags $(BUILD_LDFLAGS) -o $(GOPATH)/bin/minio
|
||||
@go build --ldflags $(BUILD_LDFLAGS) -o $(GOPATH)/bin/minio
|
||||
|
||||
pkg-add:
|
||||
@GO15VENDOREXPERIMENT=1 ${GOPATH}/bin/govendor add $(PKG)
|
||||
${GOPATH}/bin/govendor add $(PKG)
|
||||
|
||||
pkg-update:
|
||||
@GO15VENDOREXPERIMENT=1 ${GOPATH}/bin/govendor update $(PKG)
|
||||
${GOPATH}/bin/govendor update $(PKG)
|
||||
|
||||
pkg-remove:
|
||||
@GO15VENDOREXPERIMENT=1 ${GOPATH}/bin/govendor remove $(PKG)
|
||||
${GOPATH}/bin/govendor remove $(PKG)
|
||||
|
||||
pkg-list:
|
||||
@GO15VENDOREXPERIMENT=1 $(GOPATH)/bin/govendor list
|
||||
@$(GOPATH)/bin/govendor list
|
||||
|
||||
install: gomake-all
|
||||
|
||||
@@ -139,8 +141,6 @@ experimental: verifiers
|
||||
|
||||
clean:
|
||||
@echo "Cleaning up all the generated files:"
|
||||
@rm -fv minio minio.test cover.out
|
||||
@find . -name '*.test' | xargs rm -fv
|
||||
@rm -rf isa-l
|
||||
@rm -rf build
|
||||
@rm -rf release
|
||||
|
||||
@@ -1,191 +1,102 @@
|
||||
# Minio Quickstart Guide [](https://gitter.im/minio/minio?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://codecov.io/gh/minio/minio)
|
||||
# Minio Quickstart Guide [](https://slack.minio.io) [](https://goreportcard.com/report/minio/minio) [](https://hub.docker.com/r/minio/minio/) [](https://codecov.io/gh/minio/minio)
|
||||
|
||||
Minio is an object storage server released under Apache License v2.0. It is compatible with Amazon S3 cloud storage service. It is best suited for storing unstructured data such as photos, videos, log files, backups and container / VM images. Size of an object can range from a few KBs to a maximum of 5TB.
|
||||
|
||||
## 1. Download
|
||||
|
||||
Minio server is light enough to be bundled with the application stack, similar to NodeJS, Redis and MySQL.
|
||||
|
||||
## Docker Container
|
||||
### Stable
|
||||
```
|
||||
docker pull minio/minio
|
||||
docker run -p 9000:9000 minio/minio server /export
|
||||
```
|
||||
|
||||
### Edge
|
||||
```
|
||||
docker pull minio/minio:edge
|
||||
docker run -p 9000:9000 minio/minio:edge server /export
|
||||
```
|
||||
Please visit Minio Docker quickstart guide for more [here](https://docs.minio.io/docs/minio-docker-quickstart-guide)
|
||||
|
||||
## OS X
|
||||
### Homebrew
|
||||
Install minio packages using [Homebrew](http://brew.sh/)
|
||||
|
||||
```sh
|
||||
brew install minio
|
||||
minio server ~/Photos
|
||||
```
|
||||
|
||||
### Binary Download
|
||||
| Platform| Architecture | URL|
|
||||
| ----------| -------- | ------|
|
||||
|Apple OS X|64-bit Intel|https://dl.minio.io/server/minio/release/darwin-amd64/minio|
|
||||
```sh
|
||||
chmod 755 minio
|
||||
./minio server ~/Photos
|
||||
```
|
||||
|
||||
## GNU/Linux
|
||||
### Binary Download
|
||||
| Platform| Architecture | URL|
|
||||
| ----------| -------- | ------|
|
||||
|GNU/Linux|64-bit Intel|https://dl.minio.io/server/minio/release/linux-amd64/minio|
|
||||
||32-bit Intel|https://dl.minio.io/server/minio/release/linux-386/minio|
|
||||
||32-bit ARM|https://dl.minio.io/server/minio/release/linux-arm/minio|
|
||||
|Apple OS X|64-bit Intel|https://dl.minio.io/server/minio/release/darwin-amd64/minio|
|
||||
||64-bit ARM|https://dl.minio.io/server/minio/release/linux-arm64/minio|
|
||||
||32-bit ARMv6|https://dl.minio.io/server/minio/release/linux-arm6vl/minio|
|
||||
```sh
|
||||
chmod +x minio
|
||||
./minio server ~/Photos
|
||||
```
|
||||
|
||||
## Microsoft Windows
|
||||
### Binary Download
|
||||
| Platform| Architecture | URL|
|
||||
| ----------| -------- | ------|
|
||||
|Microsoft Windows|64-bit|https://dl.minio.io/server/minio/release/windows-amd64/minio.exe|
|
||||
||32-bit|https://dl.minio.io/server/minio/release/windows-386/minio.exe|
|
||||
|FreeBSD|64-bit|https://dl.minio.io/server/minio/release/freebsd-amd64/minio|
|
||||
```sh
|
||||
minio.exe server D:\Photos
|
||||
```
|
||||
|
||||
### Install from Source
|
||||
## FreeBSD
|
||||
### Binary Download
|
||||
| Platform| Architecture | URL|
|
||||
| ----------| -------- | ------|
|
||||
|FreeBSD|64-bit|https://dl.minio.io/server/minio/release/freebsd-amd64/minio|
|
||||
```sh
|
||||
chmod 755 minio
|
||||
./minio server ~/Photos
|
||||
```
|
||||
|
||||
You can run Minio on FreeBSD with FreeNAS storage-backend - see [here](https://docs.minio.io/docs/how-to-run-minio-in-freenas) for more details.
|
||||
|
||||
## Install from Source
|
||||
|
||||
Source installation is only intended for developers and advanced users. If you do not have a working Golang environment, please follow [How to install Golang](https://docs.minio.io/docs/how-to-install-golang).
|
||||
|
||||
|
||||
```sh
|
||||
|
||||
$ go get -d github.com/minio/minio
|
||||
$ cd $GOPATH/src/github.com/minio/minio
|
||||
$ make
|
||||
go get -u github.com/minio/minio
|
||||
|
||||
```
|
||||
|
||||
## 2. Run Minio Server
|
||||
|
||||
|
||||
### GNU/Linux
|
||||
|
||||
```sh
|
||||
|
||||
$ chmod +x minio
|
||||
$ ./minio --help
|
||||
$ ./minio server ~/Photos
|
||||
|
||||
Endpoint: http://10.0.0.10:9000 http://127.0.0.1:9000 http://172.17.0.1:9000
|
||||
AccessKey: USWUXHGYZQYFYFFIT3RE
|
||||
SecretKey: MOJRH0mkL1IPauahWITSVvyDrQbEEIwljvmxdq03
|
||||
Region: us-east-1
|
||||
|
||||
Browser Access:
|
||||
http://10.0.0.10:9000 http://127.0.0.1:9000 http://172.17.0.1:9000
|
||||
|
||||
Command-line Access: https://docs.minio.io/docs/minio-client-quickstart-guide
|
||||
$ mc config host add myminio http://10.0.0.10:9000 USWUXHGYZQYFYFFIT3RE MOJRH0mkL1IPauahWITSVvyDrQbEEIwljvmxdq03
|
||||
|
||||
Object API (Amazon S3 compatible):
|
||||
Go: https://docs.minio.io/docs/golang-client-quickstart-guide
|
||||
Java: https://docs.minio.io/docs/java-client-quickstart-guide
|
||||
Python: https://docs.minio.io/docs/python-client-quickstart-guide
|
||||
JavaScript: https://docs.minio.io/docs/javascript-client-quickstart-guide
|
||||
|
||||
```
|
||||
|
||||
### OS X
|
||||
|
||||
|
||||
```sh
|
||||
|
||||
$ chmod 755 minio
|
||||
$ ./minio --help
|
||||
$ ./minio server ~/Photos
|
||||
|
||||
Endpoint: http://10.0.0.10:9000 http://127.0.0.1:9000 http://172.17.0.1:9000
|
||||
AccessKey: USWUXHGYZQYFYFFIT3RE
|
||||
SecretKey: MOJRH0mkL1IPauahWITSVvyDrQbEEIwljvmxdq03
|
||||
Region: us-east-1
|
||||
|
||||
Browser Access:
|
||||
http://10.0.0.10:9000 http://127.0.0.1:9000 http://172.17.0.1:9000
|
||||
|
||||
Command-line Access: https://docs.minio.io/docs/minio-client-quickstart-guide
|
||||
$ mc config host add myminio http://10.0.0.10:9000 USWUXHGYZQYFYFFIT3RE MOJRH0mkL1IPauahWITSVvyDrQbEEIwljvmxdq03
|
||||
|
||||
Object API (Amazon S3 compatible):
|
||||
Go: https://docs.minio.io/docs/golang-client-quickstart-guide
|
||||
Java: https://docs.minio.io/docs/java-client-quickstart-guide
|
||||
Python: https://docs.minio.io/docs/python-client-quickstart-guide
|
||||
JavaScript: https://docs.minio.io/docs/javascript-client-quickstart-guide
|
||||
|
||||
```
|
||||
|
||||
### Microsoft Windows
|
||||
|
||||
```sh
|
||||
|
||||
C:\Users\Username\Downloads> minio.exe --help
|
||||
C:\Users\Username\Downloads> minio.exe server D:\Photos
|
||||
|
||||
Endpoint: http://10.0.0.10:9000 http://127.0.0.1:9000 http://172.17.0.1:9000
|
||||
AccessKey: USWUXHGYZQYFYFFIT3RE
|
||||
SecretKey: MOJRH0mkL1IPauahWITSVvyDrQbEEIwljvmxdq03
|
||||
Region: us-east-1
|
||||
|
||||
Browser Access:
|
||||
http://10.0.0.10:9000 http://127.0.0.1:9000 http://172.17.0.1:9000
|
||||
|
||||
Command-line Access: https://docs.minio.io/docs/minio-client-quickstart-guide
|
||||
$ mc.exe config host add myminio http://10.0.0.10:9000 USWUXHGYZQYFYFFIT3RE MOJRH0mkL1IPauahWITSVvyDrQbEEIwljvmxdq03
|
||||
|
||||
Object API (Amazon S3 compatible):
|
||||
Go: https://docs.minio.io/docs/golang-client-quickstart-guide
|
||||
Java: https://docs.minio.io/docs/java-client-quickstart-guide
|
||||
Python: https://docs.minio.io/docs/python-client-quickstart-guide
|
||||
JavaScript: https://docs.minio.io/docs/javascript-client-quickstart-guide
|
||||
|
||||
|
||||
```
|
||||
|
||||
### Docker Container
|
||||
|
||||
```sh
|
||||
|
||||
$ docker pull minio/minio
|
||||
$ docker run -p 9000:9000 minio/minio
|
||||
|
||||
```
|
||||
|
||||
### FreeBSD
|
||||
|
||||
```sh
|
||||
|
||||
$ chmod 755 minio
|
||||
$ ./minio --help
|
||||
$ ./minio server ~/Photos
|
||||
|
||||
Endpoint: http://10.0.0.10:9000 http://127.0.0.1:9000 http://172.17.0.1:9000
|
||||
AccessKey: USWUXHGYZQYFYFFIT3RE
|
||||
SecretKey: MOJRH0mkL1IPauahWITSVvyDrQbEEIwljvmxdq03
|
||||
Region: us-east-1
|
||||
|
||||
Browser Access:
|
||||
http://10.0.0.10:9000 http://127.0.0.1:9000 http://172.17.0.1:9000
|
||||
|
||||
Command-line Access: https://docs.minio.io/docs/minio-client-quickstart-guide
|
||||
$ mc config host add myminio http://10.0.0.10:9000 USWUXHGYZQYFYFFIT3RE MOJRH0mkL1IPauahWITSVvyDrQbEEIwljvmxdq03
|
||||
|
||||
Object API (Amazon S3 compatible):
|
||||
Go: https://docs.minio.io/docs/golang-client-quickstart-guide
|
||||
Java: https://docs.minio.io/docs/java-client-quickstart-guide
|
||||
Python: https://docs.minio.io/docs/python-client-quickstart-guide
|
||||
JavaScript: https://docs.minio.io/docs/javascript-client-quickstart-guide
|
||||
|
||||
|
||||
```
|
||||
|
||||
## 3. Test Minio Server using Minio Browser
|
||||
|
||||
Open a web browser and navigate to http://127.0.0.1:9000 to view your buckets on minio server.
|
||||
## Test using Minio Browser
|
||||
Minio Server comes with an embedded web based object browser. Point your web browser to http://127.0.0.1:9000 ensure your server has started successfully.
|
||||
|
||||

|
||||
|
||||
## Test using Minio Client `mc`
|
||||
`mc` provides a modern alternative to UNIX commands like ls, cat, cp, mirror, diff etc. It supports filesystems and Amazon S3 compatible cloud storage services. Follow the Minio Client [Quickstart Guide](https://docs.minio.io/docs/minio-client-quickstart-guide) for further instructions.
|
||||
|
||||
## 4. Test Minio Server using `mc`
|
||||
|
||||
|
||||
Install mc from [here](https://docs.minio.io/docs/minio-client-quickstart-guide). Use `mc ls` command to list all the buckets on your minio server.
|
||||
|
||||
```sh
|
||||
|
||||
$ mc ls myminio/
|
||||
[2015-08-05 08:13:22 IST] 0B andoria/
|
||||
[2015-08-05 06:14:26 IST] 0B deflector/
|
||||
[2015-08-05 08:13:11 IST] 0B ferenginar/
|
||||
[2016-03-08 14:56:35 IST] 0B jarjarbing/
|
||||
[2016-01-20 16:07:41 IST] 0B my.minio.io/
|
||||
|
||||
```
|
||||
|
||||
For more examples please navigate to [Minio Client Complete Guide](https://docs.minio.io/docs/minio-client-complete-guide).
|
||||
|
||||
|
||||
## 5. Explore Further
|
||||
|
||||
## Explore Further
|
||||
- [Minio Erasure Code QuickStart Guide](https://docs.minio.io/docs/minio-erasure-code-quickstart-guide)
|
||||
- [Minio Docker Quickstart Guide](https://docs.minio.io/docs/minio-docker-quickstart-guide)
|
||||
- [Use `mc` with Minio Server](https://docs.minio.io/docs/minio-client-quickstart-guide)
|
||||
- [Use `aws-cli` with Minio Server](https://docs.minio.io/docs/aws-cli-with-minio)
|
||||
- [Use `s3cmd` with Minio Server](https://docs.minio.io/docs/s3cmd-with-minio)
|
||||
- [Use `minio-go` SDK with Minio Server](https://docs.minio.io/docs/golang-client-quickstart-guide)
|
||||
- [The Minio documentation website](https://docs.minio.io)
|
||||
|
||||
|
||||
## 6. Contribute to Minio Project
|
||||
## Contribute to Minio Project
|
||||
Please follow Minio [Contributor's Guide](https://github.com/minio/minio/blob/master/CONTRIBUTING.md)
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
# Minio 快速入门 [](https://slack.minio.io) [](https://goreportcard.com/report/minio/minio) [](https://codecov.io/gh/minio/minio)
|
||||
|
||||
Minio是一个对象存储服务,基于Apache License v2.0协议. 它完全兼容亚马逊的S3云储存服务,非常适合于存储很多非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。
|
||||
|
||||
## 1. 下载
|
||||
|
||||
Minio是一个非常轻量的服务,可以很简单的和其他应用的结合,类似 NodeJS, Redis 或者 MySQL.
|
||||
|
||||
| Platform| Architecture | URL|
|
||||
| ----------| -------- | ------|
|
||||
|GNU/Linux|64-bit Intel|https://dl.minio.io/server/minio/release/linux-amd64/minio|
|
||||
||32-bit Intel|https://dl.minio.io/server/minio/release/linux-386/minio|
|
||||
||32-bit ARM|https://dl.minio.io/server/minio/release/linux-arm/minio|
|
||||
||64-bit ARM|https://dl.minio.io/server/minio/release/linux-arm64/minio|
|
||||
||32-bit ARMv6|https://dl.minio.io/server/minio/release/linux-arm6vl/minio|
|
||||
|Apple OS X|64-bit Intel|https://dl.minio.io/server/minio/release/darwin-amd64/minio|
|
||||
|Microsoft Windows|64-bit|https://dl.minio.io/server/minio/release/windows-amd64/minio.exe|
|
||||
||32-bit|https://dl.minio.io/server/minio/release/windows-386/minio.exe|
|
||||
|FreeBSD|64-bit|https://dl.minio.io/server/minio/release/freebsd-amd64/minio|
|
||||
|
||||
### Homebrew 安装
|
||||
|
||||
使用[Homebrew](http://brew.sh/) 来安装minio
|
||||
|
||||
```sh
|
||||
$ brew install minio
|
||||
$ minio --help
|
||||
```
|
||||
|
||||
### 源码安装
|
||||
|
||||
源码安装只针对开发者和一些高级用户,如果你还没有golang的环境,请安装golang官网安装[How to install Golang](https://docs.minio.io/docs/zh-CN/how-to-install-golang).
|
||||
|
||||
```sh
|
||||
|
||||
$ go get -u github.com/minio/minio
|
||||
|
||||
|
||||
```
|
||||
|
||||
## 2. 运行Minio服务
|
||||
|
||||
|
||||
### GNU/Linux
|
||||
|
||||
```sh
|
||||
|
||||
$ chmod +x minio
|
||||
$ ./minio --help
|
||||
$ ./minio server ~/Photos
|
||||
|
||||
端点: http://10.0.0.10:9000 http://127.0.0.1:9000 http://172.17.0.1:9000
|
||||
AccessKey: USWUXHGYZQYFYFFIT3RE
|
||||
SecretKey: MOJRH0mkL1IPauahWITSVvyDrQbEEIwljvmxdq03
|
||||
区域: us-east-1
|
||||
|
||||
浏览器访问入口:
|
||||
http://10.0.0.10:9000 http://127.0.0.1:9000 http://172.17.0.1:9000
|
||||
|
||||
命令行访问: https://docs.minio.io/docs/zh-CN/minio-client-quickstart-guide
|
||||
$ mc config host add myminio http://10.0.0.10:9000 USWUXHGYZQYFYFFIT3RE MOJRH0mkL1IPauahWITSVvyDrQbEEIwljvmxdq03
|
||||
|
||||
对象操作API (兼容Amazon S3):
|
||||
Go: https://docs.minio.io/docs/zh-CN/golang-client-quickstart-guide
|
||||
Java: https://docs.minio.io/docs/zh-CN/java-client-quickstart-guide
|
||||
Python: https://docs.minio.io/docs/zh-CN/python-client-quickstart-guide
|
||||
JavaScript: https://docs.minio.io/docs/zh-CN/javascript-client-quickstart-guide
|
||||
|
||||
```
|
||||
|
||||
### OS X
|
||||
|
||||
|
||||
```sh
|
||||
|
||||
$ chmod 755 minio
|
||||
$ ./minio --help
|
||||
$ ./minio server ~/Photos
|
||||
|
||||
端点: http://10.0.0.10:9000 http://127.0.0.1:9000 http://172.17.0.1:9000
|
||||
AccessKey: USWUXHGYZQYFYFFIT3RE
|
||||
SecretKey: MOJRH0mkL1IPauahWITSVvyDrQbEEIwljvmxdq03
|
||||
区域: us-east-1
|
||||
|
||||
浏览器访问入口:
|
||||
http://10.0.0.10:9000 http://127.0.0.1:9000 http://172.17.0.1:9000
|
||||
|
||||
命令行访问: https://docs.minio.io/docs/zh-CN/minio-client-quickstart-guide
|
||||
$ mc config host add myminio http://10.0.0.10:9000 USWUXHGYZQYFYFFIT3RE MOJRH0mkL1IPauahWITSVvyDrQbEEIwljvmxdq03
|
||||
|
||||
对象操作API (兼容Amazon S3):
|
||||
Go: https://docs.minio.io/docs/zh-CN/golang-client-quickstart-guide
|
||||
Java: https://docs.minio.io/docs/zh-CN/java-client-quickstart-guide
|
||||
Python: https://docs.minio.io/docs/zh-CN/python-client-quickstart-guide
|
||||
JavaScript: https://docs.minio.io/docs/zh-CN/javascript-client-quickstart-guide
|
||||
|
||||
```
|
||||
|
||||
### Microsoft Windows
|
||||
|
||||
```sh
|
||||
|
||||
C:\Users\Username\Downloads> minio.exe --help
|
||||
C:\Users\Username\Downloads> minio.exe server D:\Photos
|
||||
|
||||
端点: http://10.0.0.10:9000 http://127.0.0.1:9000 http://172.17.0.1:9000
|
||||
AccessKey: USWUXHGYZQYFYFFIT3RE
|
||||
SecretKey: MOJRH0mkL1IPauahWITSVvyDrQbEEIwljvmxdq03
|
||||
区域: us-east-1
|
||||
|
||||
浏览器访问入口:
|
||||
http://10.0.0.10:9000 http://127.0.0.1:9000 http://172.17.0.1:9000
|
||||
|
||||
命令行访问: https://docs.minio.io/docs/zh-CN/minio-client-quickstart-guide
|
||||
$ mc.exe config host add myminio http://10.0.0.10:9000 USWUXHGYZQYFYFFIT3RE MOJRH0mkL1IPauahWITSVvyDrQbEEIwljvmxdq03
|
||||
|
||||
对象操作API (兼容Amazon S3):
|
||||
Go: https://docs.minio.io/docs/zh-CN/golang-client-quickstart-guide
|
||||
Java: https://docs.minio.io/docs/zh-CN/java-client-quickstart-guide
|
||||
Python: https://docs.minio.io/docs/zh-CN/python-client-quickstart-guide
|
||||
JavaScript: https://docs.minio.io/docs/zh-CN/javascript-client-quickstart-guide
|
||||
|
||||
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
```sh
|
||||
|
||||
$ docker pull minio/minio
|
||||
$ docker run -p 9000:9000 minio/minio
|
||||
|
||||
```
|
||||
访问minio的docker入门指南获得更多内容 [here](https://docs.minio.io/docs/zh-CN/minio-docker-quickstart-guide)
|
||||
|
||||
### FreeBSD
|
||||
|
||||
```sh
|
||||
|
||||
$ chmod 755 minio
|
||||
$ ./minio --help
|
||||
$ ./minio server ~/Photos
|
||||
|
||||
端点: http://10.0.0.10:9000 http://127.0.0.1:9000 http://172.17.0.1:9000
|
||||
AccessKey: USWUXHGYZQYFYFFIT3RE
|
||||
SecretKey: MOJRH0mkL1IPauahWITSVvyDrQbEEIwljvmxdq03
|
||||
区域: us-east-1
|
||||
|
||||
浏览器访问入口:
|
||||
http://10.0.0.10:9000 http://127.0.0.1:9000 http://172.17.0.1:9000
|
||||
|
||||
命令行访问: https://docs.minio.io/docs/zh-CN/minio-client-quickstart-guide
|
||||
$ mc config host add myminio http://10.0.0.10:9000 USWUXHGYZQYFYFFIT3RE MOJRH0mkL1IPauahWITSVvyDrQbEEIwljvmxdq03
|
||||
|
||||
对象操作API (兼容Amazon S3):
|
||||
Go: https://docs.minio.io/docs/zh-CN/golang-client-quickstart-guide
|
||||
Java: https://docs.minio.io/docs/zh-CN/java-client-quickstart-guide
|
||||
Python: https://docs.minio.io/docs/zh-CN/python-client-quickstart-guide
|
||||
JavaScript: https://docs.minio.io/docs/zh-CN/javascript-client-quickstart-guide
|
||||
|
||||
|
||||
```
|
||||
请访问FreeBSD的官网指南获取更多详细信息[here](https://www.freebsd.org/doc/handbook/zfs-quickstart.html)
|
||||
|
||||
## 3. 使用浏览器测试minio服务
|
||||
|
||||
打开浏览器并输入 http://127.0.0.1:9000 查看在minio服务器上面的所有bucket
|
||||
|
||||

|
||||
|
||||
|
||||
## 4. 使用`mc`测试minio服务
|
||||
|
||||
|
||||
按照 [这个](https://docs.minio.io/docs/minio-client-quickstart-guide) 安装mc. 使用 `mc ls` 命令显示所有在minio服务上面的bucket.
|
||||
|
||||
```sh
|
||||
|
||||
$ mc ls myminio/
|
||||
[2015-08-05 08:13:22 IST] 0B andoria/
|
||||
[2015-08-05 06:14:26 IST] 0B deflector/
|
||||
[2015-08-05 08:13:11 IST] 0B ferenginar/
|
||||
[2016-03-08 14:56:35 IST] 0B jarjarbing/
|
||||
[2016-01-20 16:07:41 IST] 0B my.minio.io/
|
||||
|
||||
```
|
||||
|
||||
查看更多的例子请访问 [Minio Client Complete Guide](https://docs.minio.io/docs/zh-CN/minio-client-complete-guide).
|
||||
|
||||
|
||||
## 5. 更多内容
|
||||
|
||||
- [Minio Erasure Code 快速入门](https://docs.minio.io/docs/zh-CN/minio-erasure-code-quickstart-guide)
|
||||
- [Minio Docker 快速入门](https://docs.minio.io/docs/zh-CN/minio-docker-quickstart-guide)
|
||||
- [使用`mc`测试 Minio Server](https://docs.minio.io/docs/zh-CN/minio-client-quickstart-guide)
|
||||
- [使用 `aws-cli` 测试 Minio Server](https://docs.minio.io/docs/zh-CN/aws-cli-with-minio)
|
||||
- [使用 `s3cmd` 测试 Minio Server](https://docs.minio.io/docs/zh-CN/s3cmd-with-minio)
|
||||
- [使用 `minio-go` SDK ce's测试 Minio Server](https://docs.minio.io/docs/zh-CN/golang-client-quickstart-guide)
|
||||
|
||||
|
||||
## 6. 给Minio项目贡献
|
||||
请按照Minio [贡献者指导手册](https://github.com/minio/minio/blob/master/CONTRIBUTING.md)
|
||||
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// credential container for access and secret keys.
|
||||
type credential struct {
|
||||
AccessKeyID string `json:"accessKey"`
|
||||
SecretAccessKey string `json:"secretKey"`
|
||||
}
|
||||
|
||||
const (
|
||||
minioAccessID = 20
|
||||
minioSecretID = 40
|
||||
)
|
||||
|
||||
// isValidSecretKey - validate secret key.
|
||||
var isValidSecretKey = regexp.MustCompile(`^.{8,40}$`)
|
||||
|
||||
// isValidAccessKey - validate access key.
|
||||
var isValidAccessKey = regexp.MustCompile(`^[a-zA-Z0-9\\-\\.\\_\\~]{5,20}$`)
|
||||
|
||||
// mustGenAccessKeys - must generate access credentials.
|
||||
func mustGenAccessKeys() (creds credential) {
|
||||
creds, err := genAccessKeys()
|
||||
fatalIf(err, "Unable to generate access keys.")
|
||||
return creds
|
||||
}
|
||||
|
||||
// genAccessKeys - generate access credentials.
|
||||
func genAccessKeys() (credential, error) {
|
||||
accessKeyID, err := genAccessKeyID()
|
||||
if err != nil {
|
||||
return credential{}, err
|
||||
}
|
||||
secretAccessKey, err := genSecretAccessKey()
|
||||
if err != nil {
|
||||
return credential{}, err
|
||||
}
|
||||
creds := credential{
|
||||
AccessKeyID: string(accessKeyID),
|
||||
SecretAccessKey: string(secretAccessKey),
|
||||
}
|
||||
return creds, nil
|
||||
}
|
||||
|
||||
// genAccessKeyID - generate random alpha numeric value using only uppercase characters
|
||||
// takes input as size in integer
|
||||
func genAccessKeyID() ([]byte, error) {
|
||||
alpha := make([]byte, minioAccessID)
|
||||
if _, err := rand.Read(alpha); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := 0; i < minioAccessID; i++ {
|
||||
alpha[i] = alphaNumericTable[alpha[i]%byte(len(alphaNumericTable))]
|
||||
}
|
||||
return alpha, nil
|
||||
}
|
||||
|
||||
// genSecretAccessKey - generate random base64 numeric value from a random seed.
|
||||
func genSecretAccessKey() ([]byte, error) {
|
||||
rb := make([]byte, minioSecretID)
|
||||
if _, err := rand.Read(rb); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []byte(base64.StdEncoding.EncodeToString(rb))[:minioSecretID], nil
|
||||
}
|
||||
@@ -9,31 +9,45 @@ platform: x64
|
||||
|
||||
clone_folder: c:\gopath\src\github.com\minio\minio
|
||||
|
||||
# environment variables
|
||||
# Environment variables
|
||||
environment:
|
||||
GOPATH: c:\gopath
|
||||
GO15VENDOREXPERIMENT: 1
|
||||
|
||||
# scripts that run after cloning repository
|
||||
install:
|
||||
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
|
||||
- go version
|
||||
- go env
|
||||
- python --version
|
||||
|
||||
# to run your custom scripts instead of automatic MSBuild
|
||||
# To run your custom scripts instead of automatic MSBuild
|
||||
build_script:
|
||||
- go test -race .
|
||||
- go test -race github.com/minio/minio/pkg...
|
||||
- go test -coverprofile=coverage.txt -covermode=atomic
|
||||
# Compile
|
||||
- appveyor AddCompilationMessage "Starting Compile"
|
||||
- cd c:\gopath\src\github.com\minio\minio
|
||||
- go run buildscripts/gen-ldflags.go > temp.txt
|
||||
- set /p BUILD_LDFLAGS=<temp.txt
|
||||
- go build -ldflags="%BUILD_LDFLAGS%" -o %GOPATH%\bin\minio.exe
|
||||
- appveyor AddCompilationMessage "Compile Success"
|
||||
|
||||
# To run your custom scripts instead of automatic tests
|
||||
test_script:
|
||||
# Unit tests
|
||||
- ps: Add-AppveyorTest "Unit Tests" -Outcome Running
|
||||
- mkdir build\coverage
|
||||
- go test -timeout 20m -v -race github.com/minio/minio/cmd...
|
||||
- go test -v -race github.com/minio/minio/pkg...
|
||||
- go test -timeout 15m -coverprofile=build\coverage\coverage.txt -covermode=atomic github.com/minio/minio/cmd
|
||||
- ps: Update-AppveyorTest "Unit Tests" -Outcome Passed
|
||||
|
||||
after_test:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
# to disable automatic tests
|
||||
test: off
|
||||
- go tool cover -html=build\coverage\coverage.txt -o build\coverage\coverage.html
|
||||
- ps: Push-AppveyorArtifact build\coverage\coverage.txt
|
||||
- ps: Push-AppveyorArtifact build\coverage\coverage.html
|
||||
# Upload coverage report.
|
||||
- "SET PATH=C:\\Python34;C:\\Python34\\Scripts;%PATH%"
|
||||
- pip install codecov
|
||||
- codecov -X gcov -f "build\coverage\coverage.txt"
|
||||
|
||||
# to disable deployment
|
||||
deploy: off
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"presets": [
|
||||
"es2015",
|
||||
"react"
|
||||
],
|
||||
|
||||
"plugins": ["transform-object-rest-spread"]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
# editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.json]
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"plugins": [
|
||||
"esformatter-jsx"
|
||||
],
|
||||
// Copied from https://github.com/royriojas/esformatter-jsx
|
||||
"jsx": {
|
||||
"formatJSX": true, //Duh! that's the default
|
||||
"attrsOnSameLineAsTag": false, // move each attribute to its own line
|
||||
"maxAttrsOnTag": 3, // if lower or equal than 3 attributes, they will be kept on a single line
|
||||
"firstAttributeOnSameLine": true, // keep the first attribute in the same line as the tag
|
||||
"formatJSXExpressions": true, // default true, if false jsxExpressions won't be recursively formatted
|
||||
"JSXExpressionsSingleLine": true, // default true, if false the JSXExpressions might span several lines
|
||||
"alignWithFirstAttribute": false, // do not align attributes with the first tag
|
||||
"spaceInJSXExpressionContainers": " ", // default to one space. Make it empty if you don't like spaces between JSXExpressionContainers
|
||||
"removeSpaceBeforeClosingJSX": false, // default false. if true <React.Something /> => <React.Something/>
|
||||
"closingTagOnNewLine": false, // default false. if true attributes on multiple lines will close the tag on a new line
|
||||
"JSXAttributeQuotes": "", // possible values "single" or "double". Leave it as empty string if you don't want to modify the attributes' quotes
|
||||
"htmlOptions": {
|
||||
// put here the options for js-beautify.html
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
**/*.swp
|
||||
cover.out
|
||||
*~
|
||||
minio
|
||||
!*/
|
||||
site/
|
||||
**/*.test
|
||||
**/*.sublime-workspace
|
||||
/.idea/
|
||||
/Minio.iml
|
||||
**/access.log
|
||||
build
|
||||
vendor/**/*.js
|
||||
vendor/**/*.json
|
||||
release
|
||||
.DS_Store
|
||||
*.syso
|
||||
coverage.txt
|
||||
node_modules
|
||||
production
|
||||
@@ -0,0 +1,72 @@
|
||||
# Minio File Browser
|
||||
|
||||
``Minio Browser`` provides minimal set of UI to manage buckets and objects on ``minio`` server. ``Minio Browser`` is written in javascript and released under [Apache 2.0 License](./LICENSE).
|
||||
|
||||
## Installation
|
||||
|
||||
### Install yarn
|
||||
```sh
|
||||
curl -o- -L https://yarnpkg.com/install.sh | bash
|
||||
yarn
|
||||
```
|
||||
|
||||
### Install `go-bindata` and `go-bindata-assetfs`
|
||||
|
||||
If you do not have a working Golang environment, please follow [Install Golang](https://docs.minio.io/docs/how-to-install-golang)
|
||||
|
||||
```sh
|
||||
go get github.com/jteeuwen/go-bindata/...
|
||||
go get github.com/elazarl/go-bindata-assetfs/...
|
||||
```
|
||||
|
||||
## Generating Assets
|
||||
|
||||
### Generate ui-assets.go
|
||||
|
||||
```sh
|
||||
yarn release
|
||||
```
|
||||
|
||||
This generates ui-assets.go in the current direcotry. Now do `make` in the parent directory to build the minio binary with the newly generated ``ui-assets.go``
|
||||
|
||||
### Run Minio Browser with live reload
|
||||
|
||||
```sh
|
||||
yarn dev
|
||||
```
|
||||
|
||||
Open [http://localhost:8080/minio/](http://localhost:8080/minio/) in your browser to play with the application
|
||||
|
||||
### Run Minio Browser with live reload on custom port
|
||||
|
||||
Edit `browser/webpack.config.js`
|
||||
|
||||
```diff
|
||||
diff --git a/browser/webpack.config.js b/browser/webpack.config.js
|
||||
index 3ccdaba..9496c56 100644
|
||||
--- a/browser/webpack.config.js
|
||||
+++ b/browser/webpack.config.js
|
||||
@@ -58,6 +58,7 @@ var exports = {
|
||||
historyApiFallback: {
|
||||
index: '/minio/'
|
||||
},
|
||||
+ port: 8888,
|
||||
proxy: {
|
||||
'/minio/webrpc': {
|
||||
target: 'http://localhost:9000',
|
||||
@@ -97,7 +98,7 @@ var exports = {
|
||||
if (process.env.NODE_ENV === 'dev') {
|
||||
exports.entry = [
|
||||
'webpack/hot/dev-server',
|
||||
- 'webpack-dev-server/client?http://localhost:8080',
|
||||
+ 'webpack-dev-server/client?http://localhost:8888',
|
||||
path.resolve(__dirname, 'app/index.js')
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
```sh
|
||||
yarn dev
|
||||
```
|
||||
|
||||
Open [http://localhost:8888/minio/](http://localhost:8888/minio/) in your browser to play with the application
|
||||
@@ -0,0 +1,98 @@
|
||||
.page-load {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: #32393F;
|
||||
z-index: 100;
|
||||
transition: opacity 200ms;
|
||||
-webkit-transition: opacity 200ms;
|
||||
}
|
||||
|
||||
.pl-0{
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.pl-1 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pl-inner {
|
||||
position: absolute;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
left: 50%;
|
||||
margin-left: -50px;
|
||||
top: 50%;
|
||||
margin-top: -50px;
|
||||
text-align: center;
|
||||
-webkit-animation: fade-in 500ms;
|
||||
animation: fade-in 500ms;
|
||||
-webkit-animation-fill-mode: both;
|
||||
animation-fill-mode: both;
|
||||
animation-delay: 350ms;
|
||||
-webkit-animation-delay: 350ms;
|
||||
-webkit-backface-visibility: visible;
|
||||
backface-visibility: visible;
|
||||
}
|
||||
|
||||
.pl-inner:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
display: block;
|
||||
-webkit-animation: spin 1000ms infinite linear;
|
||||
animation: spin 1000ms infinite linear;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);;
|
||||
border-left-color: #fff;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.pl-inner > img {
|
||||
width: 30px;
|
||||
margin-top: 28px;
|
||||
}
|
||||
|
||||
@-webkit-keyframes fade-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="139.0389584668397 284.78404581828653 12.617622649141168 6.417622649141265"><defs><path d="M139.04 290.7L144.95 284.78L145.46 285.29L139.54 291.2L139.04 290.7Z" id="NsdmgIWbGe"></path><path d="M145.24 285.29L151.15 291.2L151.66 290.7L145.74 284.78L145.24 285.29Z" id="VqPWmhvQEo"></path></defs><g visibility="inherit"><g><use xlink:href="#NsdmgIWbGe" opacity="1" fill="#000000" fill-opacity="1"></use></g><g><use xlink:href="#VqPWmhvQEo" opacity="1" fill="#000000" fill-opacity="1"></use></g></g></svg>
|
||||
|
After Width: | Height: | Size: 797 B |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewbox="0 0 160 256"
|
||||
version="1.1"
|
||||
id="svg3092"
|
||||
height="218.14844"
|
||||
width="137">
|
||||
<defs
|
||||
id="defs3094" />
|
||||
<metadata
|
||||
id="metadata3097">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(0.99999967,-982.85149)"
|
||||
id="layer1">
|
||||
<g
|
||||
transform="matrix(1.0112586,0,0,1.0112586,5.4603732,-13.223714)"
|
||||
id="g4144">
|
||||
<g
|
||||
id="g4140">
|
||||
<g
|
||||
style="image-rendering:auto"
|
||||
id="minio"
|
||||
transform="matrix(1.0000023,0,0,0.99999799,-739.31646,295.2269)">
|
||||
<title
|
||||
id="title3337">Minio Logo</title>
|
||||
<path
|
||||
d="m 803.42903,801.80813 c 12.40802,4.17067 27.0499,9.11665 37.95186,12.80906 -12.01295,-21.20683 -27.84305,-34.11687 -37.95186,-40.78578 l 0,27.97672 z m 0,93.72113 -14.22303,8.96217 0,-92.45864 c -1.52985,-0.5139 -2.97948,-0.9981 -4.33405,-1.45259 -19.98593,-6.67189 -32.7207,-17.95703 -35.85168,-31.77904 -2.54577,-11.21386 1.55064,-23.02184 11.24654,-32.39691 8.84929,-8.55225 21.22761,-18.39964 31.17304,-26.31619 3.60329,-2.86658 6.73129,-5.3173 9.2028,-7.39669 2.31406,-1.93977 1.61598,-4.95488 0.57033,-6.21441 -1.74073,-2.09127 -4.61921,-1.74669 -6.56195,-0.32379 -0.10398,0.0802 -5.65595,4.40832 -5.65595,4.40832 l -8.58195,-11.57033 c 0,0 5.60843,-4.14096 5.8223,-4.30137 8.39777,-6.155 19.54034,-4.98758 25.92406,2.71509 3.19039,3.84093 4.68459,8.69779 4.20929,13.67051 -0.47232,4.9549 -2.84579,9.43153 -6.68078,12.5922 -2.58439,2.12988 -5.73912,4.64298 -9.39291,7.54522 -9.70779,7.72641 -21.78905,17.33915 -30.14226,25.41907 -6.1253,5.9233 -8.70671,12.67834 -7.26896,19.02345 1.9873,8.75424 11.33268,16.34105 26.32213,21.37911 l 0,-46.22486 c 40.29563,13.62298 68.76248,61.22321 78.20589,87.64039 0,0 -41.76308,-14.15768 -63.98286,-21.64051 l 0,78.7198"
|
||||
style="fill:#f8f8f8;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path48" />
|
||||
<path
|
||||
d="m 803.42903,826.12513 -14.22303,-4.78261 0,-9.30973 14.22303,4.77667 0,9.31567"
|
||||
style="fill:#cdccca;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path50" />
|
||||
<path
|
||||
d="m 734.75566,745.06155 c -0.23469,0.16636 -0.54956,0.14853 -0.73077,-0.0743 -0.17823,-0.22576 -0.13063,-0.53766 0.0802,-0.73373 4.93113,-4.51525 24.45661,-23.86844 46.30805,-45.2624 l 8.58193,11.57033 c 0,0 -53.54135,34.01288 -54.23942,34.50007"
|
||||
style="fill:#f14621;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path52" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" width="16" height="4" viewBox="4 10 16 4"><defs><path d="M4 12C4 13.1 4.9 14 6 14C7.1 14 8 13.1 8 12C8 10.9 7.1 10 6 10C4.9 10 4 10.9 4 12ZM16 12C16 13.1 16.9 14 18 14C19.1 14 20 13.1 20 12C20 10.9 19.1 10 18 10C16.9 10 16 10.9 16 12ZM10 12C10 13.1 10.9 14 12 14C13.1 14 14 13.1 14 12C14 10.9 13.1 10 12 10C10.9 10 10 10.9 10 12Z" id="mccsKZxKL3"></path></defs><g visibility="visible"><g><use xlink:href="#mccsKZxKL3" opacity="1" fill="#eaeaea" fill-opacity="1"></use><g><use xlink:href="#mccsKZxKL3" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g></g></svg>
|
||||
|
After Width: | Height: | Size: 894 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M6 10c-1.1 0-2 0.9-2 2s0.9 2 2 2 2-0.9 2-2-0.9-2-2-2zm12 0c-1.1 0-2 0.9-2 2s0.9 2 2 2 2-0.9 2-2-0.9-2-2-2zm-6 0c-1.1 0-2 0.9-2 2s0.9 2 2 2 2-0.9 2-2-0.9-2-2-2z"/></svg>
|
||||
|
After Width: | Height: | Size: 261 B |
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" width="9" height="9" viewBox="326.76441742035513 536.0133077721175 13 13"><defs><path d="M339.76 536.01L326.76 549.01L339.76 549.01L339.76 536.01Z" id="kt3PSf43ua"></path></defs><g visibility="visible"><g><use xlink:href="#kt3PSf43ua" opacity="1" fill="#dadada" fill-opacity="1"></use></g></g></svg>
|
||||
|
After Width: | Height: | Size: 586 B |
@@ -0,0 +1,56 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Minio Browser</title>
|
||||
<link rel="stylesheet" href="/minio/loader.css" type="text/css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="page-load">
|
||||
<div class="pl-inner">
|
||||
<img src="/minio/logo.svg" alt="">
|
||||
</div>
|
||||
</div>
|
||||
<div id="root"></div>
|
||||
|
||||
<!--[if lt IE 11]>
|
||||
<div class="ie-warning">
|
||||
<div class="iw-inner">
|
||||
<i class="iwi-icon fa fa-warning"></i>
|
||||
|
||||
You are using Internet Explorer version 12.0 or lower. Due to security issues and lack of support for Web Standards it is highly recommended that you upgrade to a modern browser
|
||||
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<a href="http://www.google.com/chrome/">
|
||||
<img src="/minio/chrome.png" alt="">
|
||||
<div>Chrome</div>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.mozilla.org/en-US/firefox/new/">
|
||||
<img src="/minio/firefox.png" alt="">
|
||||
<div>Firefox</div>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.apple.com/safari/">
|
||||
<img src="/minio/safari.png" alt="">
|
||||
<div>Safari</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="iwi-skip">Skip & Continue</div>
|
||||
</div>
|
||||
</div>
|
||||
<![endif]-->
|
||||
|
||||
<script>currentUiVersion = 'MINIO_UI_VERSION'</script>
|
||||
<script src="/minio/index_bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import './less/main.less'
|
||||
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import thunkMiddleware from 'redux-thunk'
|
||||
import createStore from 'redux/lib/createStore'
|
||||
import applyMiddleware from 'redux/lib/applyMiddleware'
|
||||
|
||||
import Route from 'react-router/lib/Route'
|
||||
import Router from 'react-router/lib/Router'
|
||||
import browserHistory from 'react-router/lib/browserHistory'
|
||||
import IndexRoute from 'react-router/lib/IndexRoute'
|
||||
|
||||
import Provider from 'react-redux/lib/components/Provider'
|
||||
import connect from 'react-redux/lib/components/connect'
|
||||
|
||||
import Moment from 'moment'
|
||||
|
||||
import { minioBrowserPrefix } from './js/constants.js'
|
||||
import * as actions from './js/actions.js'
|
||||
import reducer from './js/reducers.js'
|
||||
|
||||
import _Login from './js/components/Login.js'
|
||||
import _Browse from './js/components/Browse.js'
|
||||
import fontAwesome from 'font-awesome/css/font-awesome.css'
|
||||
|
||||
import Web from './js/web'
|
||||
window.Web = Web
|
||||
|
||||
import storage from 'local-storage-fallback'
|
||||
|
||||
const store = applyMiddleware(thunkMiddleware)(createStore)(reducer)
|
||||
const Browse = connect(state => state)(_Browse)
|
||||
const Login = connect(state => state)(_Login)
|
||||
|
||||
let web = new Web(`${window.location.protocol}//${window.location.host}${minioBrowserPrefix}/webrpc`, store.dispatch)
|
||||
|
||||
window.web = web
|
||||
|
||||
store.dispatch(actions.setWeb(web))
|
||||
|
||||
function authNeeded(nextState, replace, cb) {
|
||||
if (web.LoggedIn()) {
|
||||
return cb()
|
||||
}
|
||||
if (location.pathname === minioBrowserPrefix || location.pathname === minioBrowserPrefix + '/') {
|
||||
replace(`${minioBrowserPrefix}/login`)
|
||||
}
|
||||
return cb()
|
||||
}
|
||||
|
||||
function authNotNeeded(nextState, replace) {
|
||||
if (web.LoggedIn()) {
|
||||
replace(`${minioBrowserPrefix}`)
|
||||
}
|
||||
}
|
||||
|
||||
const App = (props) => {
|
||||
return <div>
|
||||
{ props.children }
|
||||
</div>
|
||||
}
|
||||
|
||||
ReactDOM.render((
|
||||
<Provider store={ store } web={ web }>
|
||||
<Router history={ browserHistory }>
|
||||
<Route path='/' component={ App }>
|
||||
<Route path='minio' component={ App }>
|
||||
<IndexRoute component={ Browse } onEnter={ authNeeded } />
|
||||
<Route path='login' component={ Login } onEnter={ authNotNeeded } />
|
||||
<Route path=':bucket' component={ Browse } onEnter={ authNeeded } />
|
||||
<Route path=':bucket/*' component={ Browse } onEnter={ authNeeded } />
|
||||
</Route>
|
||||
</Route>
|
||||
</Router>
|
||||
</Provider>
|
||||
), document.getElementById('root'))
|
||||
|
||||
//Page loader
|
||||
let delay = [0, 400]
|
||||
let i = 0
|
||||
|
||||
function handleLoader() {
|
||||
if (i < 2) {
|
||||
setTimeout(function() {
|
||||
document.querySelector('.page-load').classList.add('pl-' + i)
|
||||
i++
|
||||
handleLoader()
|
||||
}, delay[i])
|
||||
}
|
||||
}
|
||||
handleLoader()
|
||||
|
||||
if (storage.getItem('newlyUpdated')) {
|
||||
store.dispatch(actions.showAlert({
|
||||
type: 'success',
|
||||
message: "Updated to the latest UI Version."
|
||||
}))
|
||||
storage.removeItem('newlyUpdated')
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import expect from 'expect';
|
||||
import JSONrpc from '../jsonrpc';
|
||||
|
||||
describe('jsonrpc', () => {
|
||||
it('should fail with invalid endpoint', (done) => {
|
||||
try {
|
||||
let jsonRPC = new JSONrpc({
|
||||
endpoint: 'htt://localhost:9000',
|
||||
namespace: 'Test'
|
||||
});
|
||||
} catch (e) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
it('should succeed with valid endpoint', () => {
|
||||
let jsonRPC = new JSONrpc({
|
||||
endpoint: 'http://localhost:9000/webrpc',
|
||||
namespace: 'Test'
|
||||
});
|
||||
expect(jsonRPC.version).toEqual('2.0');
|
||||
expect(jsonRPC.host).toEqual('localhost');
|
||||
expect(jsonRPC.port).toEqual('9000');
|
||||
expect(jsonRPC.path).toEqual('/webrpc');
|
||||
expect(jsonRPC.scheme).toEqual('http');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,565 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import url from 'url'
|
||||
import Moment from 'moment'
|
||||
import browserHistory from 'react-router/lib/browserHistory'
|
||||
import web from './web'
|
||||
import * as utils from './utils'
|
||||
import storage from 'local-storage-fallback'
|
||||
|
||||
import { minioBrowserPrefix } from './constants'
|
||||
|
||||
export const SET_WEB = 'SET_WEB'
|
||||
export const SET_CURRENT_BUCKET = 'SET_CURRENT_BUCKET'
|
||||
export const SET_CURRENT_PATH = 'SET_CURRENT_PATH'
|
||||
export const SET_BUCKETS = 'SET_BUCKETS'
|
||||
export const ADD_BUCKET = 'ADD_BUCKET'
|
||||
export const ADD_OBJECT = 'ADD_OBJECT'
|
||||
export const SET_VISIBLE_BUCKETS = 'SET_VISIBLE_BUCKETS'
|
||||
export const SET_OBJECTS = 'SET_OBJECTS'
|
||||
export const SET_STORAGE_INFO = 'SET_STORAGE_INFO'
|
||||
export const SET_SERVER_INFO = 'SET_SERVER_INFO'
|
||||
export const SHOW_MAKEBUCKET_MODAL = 'SHOW_MAKEBUCKET_MODAL'
|
||||
export const ADD_UPLOAD = 'ADD_UPLOAD'
|
||||
export const STOP_UPLOAD = 'STOP_UPLOAD'
|
||||
export const UPLOAD_PROGRESS = 'UPLOAD_PROGRESS'
|
||||
export const SET_ALERT = 'SET_ALERT'
|
||||
export const SET_LOGIN_ERROR = 'SET_LOGIN_ERROR'
|
||||
export const SET_SHOW_ABORT_MODAL = 'SET_SHOW_ABORT_MODAL'
|
||||
export const SHOW_ABOUT = 'SHOW_ABOUT'
|
||||
export const SET_SORT_NAME_ORDER = 'SET_SORT_NAME_ORDER'
|
||||
export const SET_SORT_SIZE_ORDER = 'SET_SORT_SIZE_ORDER'
|
||||
export const SET_SORT_DATE_ORDER = 'SET_SORT_DATE_ORDER'
|
||||
export const SET_LATEST_UI_VERSION = 'SET_LATEST_UI_VERSION'
|
||||
export const SET_SIDEBAR_STATUS = 'SET_SIDEBAR_STATUS'
|
||||
export const SET_LOGIN_REDIRECT_PATH = 'SET_LOGIN_REDIRECT_PATH'
|
||||
export const SET_LOAD_BUCKET = 'SET_LOAD_BUCKET'
|
||||
export const SET_LOAD_PATH = 'SET_LOAD_PATH'
|
||||
export const SHOW_SETTINGS = 'SHOW_SETTINGS'
|
||||
export const SET_SETTINGS = 'SET_SETTINGS'
|
||||
export const SHOW_BUCKET_POLICY = 'SHOW_BUCKET_POLICY'
|
||||
export const SET_POLICIES = 'SET_POLICIES'
|
||||
export const SET_SHARE_OBJECT = 'SET_SHARE_OBJECT'
|
||||
export const DELETE_CONFIRMATION = 'DELETE_CONFIRMATION'
|
||||
export const SET_PREFIX_WRITABLE = 'SET_PREFIX_WRITABLE'
|
||||
export const REMOVE_OBJECT = 'REMOVE_OBJECT'
|
||||
|
||||
export const showDeleteConfirmation = (object) => {
|
||||
return {
|
||||
type: DELETE_CONFIRMATION,
|
||||
payload: {
|
||||
object,
|
||||
show: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const hideDeleteConfirmation = () => {
|
||||
return {
|
||||
type: DELETE_CONFIRMATION,
|
||||
payload: {
|
||||
object: '',
|
||||
show: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const showShareObject = url => {
|
||||
return {
|
||||
type: SET_SHARE_OBJECT,
|
||||
shareObject: {
|
||||
url: url,
|
||||
show: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const hideShareObject = () => {
|
||||
return {
|
||||
type: SET_SHARE_OBJECT,
|
||||
shareObject: {
|
||||
url: '',
|
||||
show: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const shareObject = (object, expiry) => (dispatch, getState) => {
|
||||
const {currentBucket, web} = getState()
|
||||
let host = location.host
|
||||
let bucket = currentBucket
|
||||
|
||||
if (!web.LoggedIn()) {
|
||||
dispatch(showShareObject(`${host}/${bucket}/${object}`))
|
||||
return
|
||||
}
|
||||
web.PresignedGet({
|
||||
host,
|
||||
bucket,
|
||||
object,
|
||||
expiry
|
||||
})
|
||||
.then(obj => {
|
||||
dispatch(showShareObject(obj.url))
|
||||
})
|
||||
.catch(err => {
|
||||
dispatch(showAlert({
|
||||
type: 'danger',
|
||||
message: err.message
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
export const setLoginRedirectPath = (path) => {
|
||||
return {
|
||||
type: SET_LOGIN_REDIRECT_PATH,
|
||||
path
|
||||
}
|
||||
}
|
||||
|
||||
export const setLoadPath = (loadPath) => {
|
||||
return {
|
||||
type: SET_LOAD_PATH,
|
||||
loadPath
|
||||
}
|
||||
}
|
||||
|
||||
export const setLoadBucket = (loadBucket) => {
|
||||
return {
|
||||
type: SET_LOAD_BUCKET,
|
||||
loadBucket
|
||||
}
|
||||
}
|
||||
|
||||
export const setWeb = web => {
|
||||
return {
|
||||
type: SET_WEB,
|
||||
web
|
||||
}
|
||||
}
|
||||
|
||||
export const setBuckets = buckets => {
|
||||
return {
|
||||
type: SET_BUCKETS,
|
||||
buckets
|
||||
}
|
||||
}
|
||||
|
||||
export const addBucket = bucket => {
|
||||
return {
|
||||
type: ADD_BUCKET,
|
||||
bucket
|
||||
}
|
||||
}
|
||||
|
||||
export const showMakeBucketModal = () => {
|
||||
return {
|
||||
type: SHOW_MAKEBUCKET_MODAL,
|
||||
showMakeBucketModal: true
|
||||
}
|
||||
}
|
||||
|
||||
export const hideAlert = () => {
|
||||
return {
|
||||
type: SET_ALERT,
|
||||
alert: {
|
||||
show: false,
|
||||
message: '',
|
||||
type: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const showAlert = alert => {
|
||||
return (dispatch, getState) => {
|
||||
let alertTimeout = null
|
||||
if (alert.type !== 'danger') {
|
||||
alertTimeout = setTimeout(() => {
|
||||
dispatch({
|
||||
type: SET_ALERT,
|
||||
alert: {
|
||||
show: false
|
||||
}
|
||||
})
|
||||
}, 5000)
|
||||
}
|
||||
dispatch({
|
||||
type: SET_ALERT,
|
||||
alert: Object.assign({}, alert, {
|
||||
show: true,
|
||||
alertTimeout
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const removeObject = object => {
|
||||
return {
|
||||
type: REMOVE_OBJECT,
|
||||
object
|
||||
}
|
||||
}
|
||||
|
||||
export const setSidebarStatus = (status) => {
|
||||
return {
|
||||
type: SET_SIDEBAR_STATUS,
|
||||
sidebarStatus: status
|
||||
}
|
||||
}
|
||||
|
||||
export const hideMakeBucketModal = () => {
|
||||
return {
|
||||
type: SHOW_MAKEBUCKET_MODAL,
|
||||
showMakeBucketModal: false
|
||||
}
|
||||
}
|
||||
|
||||
export const setVisibleBuckets = visibleBuckets => {
|
||||
return {
|
||||
type: SET_VISIBLE_BUCKETS,
|
||||
visibleBuckets
|
||||
}
|
||||
}
|
||||
|
||||
export const setObjects = (objects, marker, istruncated) => {
|
||||
return {
|
||||
type: SET_OBJECTS,
|
||||
objects,
|
||||
marker,
|
||||
istruncated
|
||||
}
|
||||
}
|
||||
|
||||
export const setCurrentBucket = currentBucket => {
|
||||
return {
|
||||
type: SET_CURRENT_BUCKET,
|
||||
currentBucket
|
||||
}
|
||||
}
|
||||
|
||||
export const setCurrentPath = currentPath => {
|
||||
return {
|
||||
type: SET_CURRENT_PATH,
|
||||
currentPath
|
||||
}
|
||||
}
|
||||
|
||||
export const setStorageInfo = storageInfo => {
|
||||
return {
|
||||
type: SET_STORAGE_INFO,
|
||||
storageInfo
|
||||
}
|
||||
}
|
||||
|
||||
export const setServerInfo = serverInfo => {
|
||||
return {
|
||||
type: SET_SERVER_INFO,
|
||||
serverInfo
|
||||
}
|
||||
}
|
||||
|
||||
const setPrefixWritable = prefixWritable => {
|
||||
return {
|
||||
type: SET_PREFIX_WRITABLE,
|
||||
prefixWritable,
|
||||
}
|
||||
}
|
||||
|
||||
export const selectBucket = (newCurrentBucket, prefix) => {
|
||||
if (!prefix)
|
||||
prefix = ''
|
||||
return (dispatch, getState) => {
|
||||
let web = getState().web
|
||||
let currentBucket = getState().currentBucket
|
||||
|
||||
if (currentBucket !== newCurrentBucket) dispatch(setLoadBucket(newCurrentBucket))
|
||||
|
||||
dispatch(setCurrentBucket(newCurrentBucket))
|
||||
dispatch(selectPrefix(prefix))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
export const listObjects = () => {
|
||||
return (dispatch, getState) => {
|
||||
const {currentBucket, currentPath, marker, objects, istruncated, web} = getState()
|
||||
if (!istruncated) return
|
||||
web.ListObjects({
|
||||
bucketName: currentBucket,
|
||||
prefix: currentPath,
|
||||
marker: marker
|
||||
})
|
||||
.then(res => {
|
||||
let objects = res.objects
|
||||
if (!objects)
|
||||
objects = []
|
||||
objects = objects.map(object => {
|
||||
object.name = object.name.replace(`${currentPath}`, '');
|
||||
return object
|
||||
})
|
||||
dispatch(setObjects(objects, res.nextmarker, res.istruncated))
|
||||
dispatch(setPrefixWritable(res.writable))
|
||||
dispatch(setLoadBucket(''))
|
||||
dispatch(setLoadPath(''))
|
||||
})
|
||||
.catch(err => {
|
||||
dispatch(showAlert({
|
||||
type: 'danger',
|
||||
message: err.message
|
||||
}))
|
||||
dispatch(setLoadBucket(''))
|
||||
dispatch(setLoadPath(''))
|
||||
// Use browserHistory.replace instead of push so that browser back button works fine.
|
||||
browserHistory.replace(`${minioBrowserPrefix}/login`)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const selectPrefix = prefix => {
|
||||
return (dispatch, getState) => {
|
||||
const {currentBucket, web} = getState()
|
||||
dispatch(setObjects([], "", false))
|
||||
dispatch(setLoadPath(prefix))
|
||||
web.ListObjects({
|
||||
bucketName: currentBucket,
|
||||
prefix,
|
||||
marker: ""
|
||||
})
|
||||
.then(res => {
|
||||
let objects = res.objects
|
||||
if (!objects)
|
||||
objects = []
|
||||
objects = objects.map(object => {
|
||||
object.name = object.name.replace(`${prefix}`, '');
|
||||
return object
|
||||
})
|
||||
dispatch(setObjects(
|
||||
objects,
|
||||
res.nextmarker,
|
||||
res.istruncated
|
||||
))
|
||||
dispatch(setPrefixWritable(res.writable))
|
||||
dispatch(setSortNameOrder(false))
|
||||
dispatch(setCurrentPath(prefix))
|
||||
dispatch(setLoadBucket(''))
|
||||
dispatch(setLoadPath(''))
|
||||
})
|
||||
.catch(err => {
|
||||
dispatch(showAlert({
|
||||
type: 'danger',
|
||||
message: err.message
|
||||
}))
|
||||
dispatch(setLoadBucket(''))
|
||||
dispatch(setLoadPath(''))
|
||||
// Use browserHistory.replace instead of push so that browser back button works fine.
|
||||
browserHistory.replace(`${minioBrowserPrefix}/login`)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const addUpload = options => {
|
||||
return {
|
||||
type: ADD_UPLOAD,
|
||||
slug: options.slug,
|
||||
size: options.size,
|
||||
xhr: options.xhr,
|
||||
name: options.name
|
||||
}
|
||||
}
|
||||
|
||||
export const stopUpload = options => {
|
||||
return {
|
||||
type: STOP_UPLOAD,
|
||||
slug: options.slug
|
||||
}
|
||||
}
|
||||
|
||||
export const uploadProgress = options => {
|
||||
return {
|
||||
type: UPLOAD_PROGRESS,
|
||||
slug: options.slug,
|
||||
loaded: options.loaded
|
||||
}
|
||||
}
|
||||
|
||||
export const setShowAbortModal = showAbortModal => {
|
||||
return {
|
||||
type: SET_SHOW_ABORT_MODAL,
|
||||
showAbortModal
|
||||
}
|
||||
}
|
||||
|
||||
export const setLoginError = () => {
|
||||
return {
|
||||
type: SET_LOGIN_ERROR,
|
||||
loginError: true
|
||||
}
|
||||
}
|
||||
|
||||
export const uploadFile = (file, xhr) => {
|
||||
return (dispatch, getState) => {
|
||||
const {currentBucket, currentPath} = getState()
|
||||
const objectName = `${currentPath}${file.name}`
|
||||
const uploadUrl = `${window.location.origin}/minio/upload/${currentBucket}/${objectName}`
|
||||
// The slug is a unique identifer for the file upload.
|
||||
const slug = `${currentBucket}-${currentPath}-${file.name}`
|
||||
|
||||
xhr.open('PUT', uploadUrl, true)
|
||||
xhr.withCredentials = false
|
||||
const token = storage.getItem('token')
|
||||
if (token) xhr.setRequestHeader("Authorization", 'Bearer ' + storage.getItem('token'))
|
||||
xhr.setRequestHeader('x-amz-date', Moment().utc().format('YYYYMMDDTHHmmss') + 'Z')
|
||||
dispatch(addUpload({
|
||||
slug,
|
||||
xhr,
|
||||
size: file.size,
|
||||
name: file.name
|
||||
}))
|
||||
|
||||
xhr.onload = function(event) {
|
||||
if (xhr.status == 401 || xhr.status == 403 || xhr.status == 500) {
|
||||
setShowAbortModal(false)
|
||||
dispatch(stopUpload({
|
||||
slug
|
||||
}))
|
||||
dispatch(showAlert({
|
||||
type: 'danger',
|
||||
message: 'Unauthorized request.'
|
||||
}))
|
||||
}
|
||||
if (xhr.status == 200) {
|
||||
setShowAbortModal(false)
|
||||
dispatch(stopUpload({
|
||||
slug
|
||||
}))
|
||||
dispatch(showAlert({
|
||||
type: 'success',
|
||||
message: 'File \'' + file.name + '\' uploaded successfully.'
|
||||
}))
|
||||
dispatch(selectPrefix(currentPath))
|
||||
}
|
||||
}
|
||||
|
||||
xhr.upload.addEventListener('error', event => {
|
||||
dispatch(showAlert({
|
||||
type: 'danger',
|
||||
message: 'Error occurred uploading \'' + file.name + '\'.'
|
||||
}))
|
||||
dispatch(stopUpload({
|
||||
slug
|
||||
}))
|
||||
})
|
||||
|
||||
xhr.upload.addEventListener('progress', event => {
|
||||
if (event.lengthComputable) {
|
||||
let loaded = event.loaded
|
||||
let total = event.total
|
||||
|
||||
// Update the counter.
|
||||
dispatch(uploadProgress({
|
||||
slug,
|
||||
loaded
|
||||
}))
|
||||
}
|
||||
})
|
||||
xhr.send(file)
|
||||
}
|
||||
}
|
||||
|
||||
export const showAbout = () => {
|
||||
return {
|
||||
type: SHOW_ABOUT,
|
||||
showAbout: true
|
||||
}
|
||||
}
|
||||
|
||||
export const hideAbout = () => {
|
||||
return {
|
||||
type: SHOW_ABOUT,
|
||||
showAbout: false
|
||||
}
|
||||
}
|
||||
|
||||
export const setSortNameOrder = (sortNameOrder) => {
|
||||
return {
|
||||
type: SET_SORT_NAME_ORDER,
|
||||
sortNameOrder
|
||||
}
|
||||
}
|
||||
|
||||
export const setSortSizeOrder = (sortSizeOrder) => {
|
||||
return {
|
||||
type: SET_SORT_SIZE_ORDER,
|
||||
sortSizeOrder
|
||||
}
|
||||
}
|
||||
|
||||
export const setSortDateOrder = (sortDateOrder) => {
|
||||
return {
|
||||
type: SET_SORT_DATE_ORDER,
|
||||
sortDateOrder
|
||||
}
|
||||
}
|
||||
|
||||
export const setLatestUIVersion = (latestUiVersion) => {
|
||||
return {
|
||||
type: SET_LATEST_UI_VERSION,
|
||||
latestUiVersion
|
||||
}
|
||||
}
|
||||
|
||||
export const showSettings = () => {
|
||||
return {
|
||||
type: SHOW_SETTINGS,
|
||||
showSettings: true
|
||||
}
|
||||
}
|
||||
|
||||
export const hideSettings = () => {
|
||||
return {
|
||||
type: SHOW_SETTINGS,
|
||||
showSettings: false
|
||||
}
|
||||
}
|
||||
|
||||
export const setSettings = (settings) => {
|
||||
return {
|
||||
type: SET_SETTINGS,
|
||||
settings
|
||||
}
|
||||
}
|
||||
|
||||
export const showBucketPolicy = () => {
|
||||
return {
|
||||
type: SHOW_BUCKET_POLICY,
|
||||
showBucketPolicy: true
|
||||
}
|
||||
}
|
||||
|
||||
export const hideBucketPolicy = () => {
|
||||
return {
|
||||
type: SHOW_BUCKET_POLICY,
|
||||
showBucketPolicy: false
|
||||
}
|
||||
}
|
||||
|
||||
export const setPolicies = (policies) => {
|
||||
return {
|
||||
type: SET_POLICIES,
|
||||
policies
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,737 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import classNames from 'classnames'
|
||||
import browserHistory from 'react-router/lib/browserHistory'
|
||||
import humanize from 'humanize'
|
||||
import Moment from 'moment'
|
||||
import Modal from 'react-bootstrap/lib/Modal'
|
||||
import ModalBody from 'react-bootstrap/lib/ModalBody'
|
||||
import ModalHeader from 'react-bootstrap/lib/ModalHeader'
|
||||
import Alert from 'react-bootstrap/lib/Alert'
|
||||
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'
|
||||
import Tooltip from 'react-bootstrap/lib/Tooltip'
|
||||
import Dropdown from 'react-bootstrap/lib/Dropdown'
|
||||
import MenuItem from 'react-bootstrap/lib/MenuItem'
|
||||
|
||||
import InputGroup from '../components/InputGroup'
|
||||
import Dropzone from '../components/Dropzone'
|
||||
import ObjectsList from '../components/ObjectsList'
|
||||
import SideBar from '../components/SideBar'
|
||||
import Path from '../components/Path'
|
||||
import BrowserUpdate from '../components/BrowserUpdate'
|
||||
import UploadModal from '../components/UploadModal'
|
||||
import SettingsModal from '../components/SettingsModal'
|
||||
import PolicyInput from '../components/PolicyInput'
|
||||
import Policy from '../components/Policy'
|
||||
import BrowserDropdown from '../components/BrowserDropdown'
|
||||
import ConfirmModal from './ConfirmModal'
|
||||
import logo from '../../img/logo.svg'
|
||||
import * as actions from '../actions'
|
||||
import * as utils from '../utils'
|
||||
import * as mime from '../mime'
|
||||
import { minioBrowserPrefix } from '../constants'
|
||||
import CopyToClipboard from 'react-copy-to-clipboard'
|
||||
import storage from 'local-storage-fallback'
|
||||
import InfiniteScroll from 'react-infinite-scroller';
|
||||
|
||||
export default class Browse extends React.Component {
|
||||
componentDidMount() {
|
||||
const {web, dispatch, currentBucket} = this.props
|
||||
if (!web.LoggedIn()) return
|
||||
web.StorageInfo()
|
||||
.then(res => {
|
||||
let storageInfo = Object.assign({}, {
|
||||
total: res.storageInfo.Total,
|
||||
free: res.storageInfo.Free
|
||||
})
|
||||
storageInfo.used = storageInfo.total - storageInfo.free
|
||||
dispatch(actions.setStorageInfo(storageInfo))
|
||||
return web.ServerInfo()
|
||||
})
|
||||
.then(res => {
|
||||
let serverInfo = Object.assign({}, {
|
||||
version: res.MinioVersion,
|
||||
memory: res.MinioMemory,
|
||||
platform: res.MinioPlatform,
|
||||
runtime: res.MinioRuntime,
|
||||
envVars: res.MinioEnvVars
|
||||
})
|
||||
dispatch(actions.setServerInfo(serverInfo))
|
||||
})
|
||||
.catch(err => {
|
||||
dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message: err.message
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
const {dispatch} = this.props
|
||||
// Clear out any stale message in the alert of Login page
|
||||
dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message: ''
|
||||
}))
|
||||
if (web.LoggedIn()) {
|
||||
web.ListBuckets()
|
||||
.then(res => {
|
||||
let buckets
|
||||
if (!res.buckets)
|
||||
buckets = []
|
||||
else
|
||||
buckets = res.buckets.map(bucket => bucket.name)
|
||||
if (buckets.length) {
|
||||
dispatch(actions.setBuckets(buckets))
|
||||
dispatch(actions.setVisibleBuckets(buckets))
|
||||
if (location.pathname === minioBrowserPrefix || location.pathname === minioBrowserPrefix + '/') {
|
||||
browserHistory.push(utils.pathJoin(buckets[0]))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
this.history = browserHistory.listen(({pathname}) => {
|
||||
let decPathname = decodeURI(pathname)
|
||||
if (decPathname === `${minioBrowserPrefix}/login`) return // FIXME: better organize routes and remove this
|
||||
if (!decPathname.endsWith('/'))
|
||||
decPathname += '/'
|
||||
if (decPathname === minioBrowserPrefix + '/') {
|
||||
return
|
||||
}
|
||||
let obj = utils.pathSlice(decPathname)
|
||||
if (!web.LoggedIn()) {
|
||||
dispatch(actions.setBuckets([obj.bucket]))
|
||||
dispatch(actions.setVisibleBuckets([obj.bucket]))
|
||||
}
|
||||
dispatch(actions.selectBucket(obj.bucket, obj.prefix))
|
||||
})
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.history()
|
||||
}
|
||||
|
||||
selectBucket(e, bucket) {
|
||||
e.preventDefault()
|
||||
if (bucket === this.props.currentBucket) return
|
||||
browserHistory.push(utils.pathJoin(bucket))
|
||||
}
|
||||
|
||||
searchBuckets(e) {
|
||||
e.preventDefault()
|
||||
let {buckets} = this.props
|
||||
this.props.dispatch(actions.setVisibleBuckets(buckets.filter(bucket => bucket.indexOf(e.target.value) > -1)))
|
||||
}
|
||||
|
||||
listObjects() {
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.listObjects())
|
||||
}
|
||||
|
||||
selectPrefix(e, prefix) {
|
||||
e.preventDefault()
|
||||
const {dispatch, currentPath, web, currentBucket} = this.props
|
||||
const encPrefix = encodeURI(prefix)
|
||||
if (prefix.endsWith('/') || prefix === '') {
|
||||
if (prefix === currentPath) return
|
||||
browserHistory.push(utils.pathJoin(currentBucket, encPrefix))
|
||||
} else {
|
||||
window.location = `${window.location.origin}/minio/download/${currentBucket}/${encPrefix}?token=${storage.getItem('token')}`
|
||||
}
|
||||
}
|
||||
|
||||
makeBucket(e) {
|
||||
e.preventDefault()
|
||||
const bucketName = this.refs.makeBucketRef.value
|
||||
this.refs.makeBucketRef.value = ''
|
||||
const {web, dispatch} = this.props
|
||||
this.hideMakeBucketModal()
|
||||
web.MakeBucket({
|
||||
bucketName
|
||||
})
|
||||
.then(() => {
|
||||
dispatch(actions.addBucket(bucketName))
|
||||
dispatch(actions.selectBucket(bucketName))
|
||||
})
|
||||
.catch(err => dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message: err.message
|
||||
})))
|
||||
}
|
||||
|
||||
hideMakeBucketModal() {
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.hideMakeBucketModal())
|
||||
}
|
||||
|
||||
showMakeBucketModal(e) {
|
||||
e.preventDefault()
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.showMakeBucketModal())
|
||||
}
|
||||
|
||||
showAbout(e) {
|
||||
e.preventDefault()
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.showAbout())
|
||||
}
|
||||
|
||||
hideAbout(e) {
|
||||
e.preventDefault()
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.hideAbout())
|
||||
}
|
||||
|
||||
showBucketPolicy(e) {
|
||||
e.preventDefault()
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.showBucketPolicy())
|
||||
}
|
||||
|
||||
hideBucketPolicy(e) {
|
||||
e.preventDefault()
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.hideBucketPolicy())
|
||||
}
|
||||
|
||||
uploadFile(e) {
|
||||
e.preventDefault()
|
||||
const {dispatch, buckets} = this.props
|
||||
|
||||
if (buckets.length === 0) {
|
||||
dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message: "Bucket needs to be created before trying to upload files."
|
||||
}))
|
||||
return
|
||||
}
|
||||
let file = e.target.files[0]
|
||||
e.target.value = null
|
||||
this.xhr = new XMLHttpRequest()
|
||||
dispatch(actions.uploadFile(file, this.xhr))
|
||||
}
|
||||
|
||||
removeObject() {
|
||||
const {web, dispatch, currentPath, currentBucket, deleteConfirmation} = this.props
|
||||
web.RemoveObject({
|
||||
bucketName: currentBucket,
|
||||
objectName: deleteConfirmation.object
|
||||
})
|
||||
.then(() => {
|
||||
this.hideDeleteConfirmation()
|
||||
dispatch(actions.removeObject(deleteConfirmation.object))
|
||||
})
|
||||
.catch(e => dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message: e.message
|
||||
})))
|
||||
}
|
||||
|
||||
hideAlert(e) {
|
||||
e.preventDefault()
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.hideAlert())
|
||||
}
|
||||
|
||||
showDeleteConfirmation(e, object) {
|
||||
e.preventDefault()
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.showDeleteConfirmation(object))
|
||||
}
|
||||
|
||||
hideDeleteConfirmation() {
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.hideDeleteConfirmation())
|
||||
}
|
||||
|
||||
shareObject(e, object) {
|
||||
e.preventDefault()
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.shareObject(object))
|
||||
}
|
||||
|
||||
hideShareObjectModal() {
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.hideShareObject())
|
||||
}
|
||||
|
||||
dataType(name, contentType) {
|
||||
return mime.getDataType(name, contentType)
|
||||
}
|
||||
|
||||
sortObjectsByName(e) {
|
||||
const {dispatch, objects, sortNameOrder} = this.props
|
||||
dispatch(actions.setObjects(utils.sortObjectsByName(objects, !sortNameOrder)))
|
||||
dispatch(actions.setSortNameOrder(!sortNameOrder))
|
||||
}
|
||||
|
||||
sortObjectsBySize() {
|
||||
const {dispatch, objects, sortSizeOrder} = this.props
|
||||
dispatch(actions.setObjects(utils.sortObjectsBySize(objects, !sortSizeOrder)))
|
||||
dispatch(actions.setSortSizeOrder(!sortSizeOrder))
|
||||
}
|
||||
|
||||
sortObjectsByDate() {
|
||||
const {dispatch, objects, sortDateOrder} = this.props
|
||||
dispatch(actions.setObjects(utils.sortObjectsByDate(objects, !sortDateOrder)))
|
||||
dispatch(actions.setSortDateOrder(!sortDateOrder))
|
||||
}
|
||||
|
||||
logout(e) {
|
||||
const {web} = this.props
|
||||
e.preventDefault()
|
||||
web.Logout()
|
||||
browserHistory.push(`${minioBrowserPrefix}/login`)
|
||||
}
|
||||
|
||||
fullScreen(e) {
|
||||
e.preventDefault()
|
||||
let el = document.documentElement
|
||||
if (el.requestFullscreen) {
|
||||
el.requestFullscreen()
|
||||
}
|
||||
if (el.mozRequestFullScreen) {
|
||||
el.mozRequestFullScreen()
|
||||
}
|
||||
if (el.webkitRequestFullscreen) {
|
||||
el.webkitRequestFullscreen()
|
||||
}
|
||||
if (el.msRequestFullscreen) {
|
||||
el.msRequestFullscreen()
|
||||
}
|
||||
}
|
||||
|
||||
toggleSidebar(status) {
|
||||
this.props.dispatch(actions.setSidebarStatus(status))
|
||||
}
|
||||
|
||||
hideSidebar(event) {
|
||||
let e = event || window.event;
|
||||
|
||||
// Support all browsers.
|
||||
let target = e.srcElement || e.target;
|
||||
if (target.nodeType === 3) // Safari support.
|
||||
target = target.parentNode;
|
||||
|
||||
let targetID = target.id;
|
||||
if (!(targetID === 'feh-trigger')) {
|
||||
this.props.dispatch(actions.setSidebarStatus(false))
|
||||
}
|
||||
}
|
||||
|
||||
showSettings(e) {
|
||||
e.preventDefault()
|
||||
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.showSettings())
|
||||
}
|
||||
|
||||
showMessage() {
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.showAlert({
|
||||
type: 'success',
|
||||
message: 'Link copied to clipboard!'
|
||||
}))
|
||||
this.hideShareObjectModal()
|
||||
}
|
||||
|
||||
selectTexts() {
|
||||
this.refs.copyTextInput.select()
|
||||
}
|
||||
|
||||
handleExpireValue(targetInput, inc) {
|
||||
inc === -1 ? this.refs[targetInput].stepDown(1) : this.refs[targetInput].stepUp(1)
|
||||
|
||||
if (this.refs.expireDays.value == 7) {
|
||||
this.refs.expireHours.value = 0
|
||||
this.refs.expireMins.value = 0
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {total, free} = this.props.storageInfo
|
||||
const {showMakeBucketModal, alert, sortNameOrder, sortSizeOrder, sortDateOrder, showAbout, showBucketPolicy} = this.props
|
||||
const {version, memory, platform, runtime} = this.props.serverInfo
|
||||
const {sidebarStatus} = this.props
|
||||
const {showSettings} = this.props
|
||||
const {policies, currentBucket, currentPath} = this.props
|
||||
const {deleteConfirmation} = this.props
|
||||
const {shareObject} = this.props
|
||||
const {web, prefixWritable, istruncated} = this.props
|
||||
|
||||
// Don't always show the SettingsModal. This is done here instead of in
|
||||
// SettingsModal.js so as to allow for #componentWillMount to handle
|
||||
// the loading of the settings.
|
||||
let settingsModal = showSettings ? <SettingsModal /> : <noscript></noscript>
|
||||
|
||||
let alertBox = <Alert className={ classNames({
|
||||
'alert': true,
|
||||
'animated': true,
|
||||
'fadeInDown': alert.show,
|
||||
'fadeOutUp': !alert.show
|
||||
}) } bsStyle={ alert.type } onDismiss={ this.hideAlert.bind(this) }>
|
||||
<div className='text-center'>
|
||||
{ alert.message }
|
||||
</div>
|
||||
</Alert>
|
||||
// Make sure you don't show a fading out alert box on the initial web-page load.
|
||||
if (!alert.message)
|
||||
alertBox = ''
|
||||
|
||||
let signoutTooltip = <Tooltip id="tt-sign-out">
|
||||
Sign out
|
||||
</Tooltip>
|
||||
let uploadTooltip = <Tooltip id="tt-upload-file">
|
||||
Upload file
|
||||
</Tooltip>
|
||||
let makeBucketTooltip = <Tooltip id="tt-create-bucket">
|
||||
Create bucket
|
||||
</Tooltip>
|
||||
let loginButton = ''
|
||||
let browserDropdownButton = ''
|
||||
let storageUsageDetails = ''
|
||||
|
||||
let used = total - free
|
||||
let usedPercent = (used / total) * 100 + '%'
|
||||
let freePercent = free * 100 / total
|
||||
|
||||
if (web.LoggedIn()) {
|
||||
browserDropdownButton = <BrowserDropdown fullScreenFunc={ this.fullScreen.bind(this) }
|
||||
aboutFunc={ this.showAbout.bind(this) }
|
||||
settingsFunc={ this.showSettings.bind(this) }
|
||||
logoutFunc={ this.logout.bind(this) } />
|
||||
} else {
|
||||
loginButton = <a className='btn btn-danger' href='/minio/login'>Login</a>
|
||||
}
|
||||
|
||||
if (web.LoggedIn()) {
|
||||
storageUsageDetails = <div className="feh-usage">
|
||||
<div className="fehu-chart">
|
||||
<div style={ { width: usedPercent } }></div>
|
||||
</div>
|
||||
<ul>
|
||||
<li>
|
||||
Used:
|
||||
{ humanize.filesize(total - free) }
|
||||
</li>
|
||||
<li className="pull-right">
|
||||
Free:
|
||||
{ humanize.filesize(total - used) }
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
}
|
||||
|
||||
let createButton = ''
|
||||
if (web.LoggedIn()) {
|
||||
createButton = <Dropdown dropup className="feb-actions" id="fe-action-toggle">
|
||||
<Dropdown.Toggle noCaret className="feba-toggle">
|
||||
<span><i className="fa fa-plus"></i></span>
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu>
|
||||
<OverlayTrigger placement="left" overlay={ uploadTooltip }>
|
||||
<a href="#" className="feba-btn feba-upload">
|
||||
<input type="file"
|
||||
onChange={ this.uploadFile.bind(this) }
|
||||
style={ { display: 'none' } }
|
||||
id="file-input"></input>
|
||||
<label htmlFor="file-input"> <i className="fa fa-cloud-upload"></i> </label>
|
||||
</a>
|
||||
</OverlayTrigger>
|
||||
<OverlayTrigger placement="left" overlay={ makeBucketTooltip }>
|
||||
<a href="#" className="feba-btn feba-bucket" onClick={ this.showMakeBucketModal.bind(this) }><i className="fa fa-hdd-o"></i></a>
|
||||
</OverlayTrigger>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
|
||||
} else {
|
||||
if (prefixWritable)
|
||||
createButton = <Dropdown dropup className="feb-actions" id="fe-action-toggle">
|
||||
<Dropdown.Toggle noCaret className="feba-toggle">
|
||||
<span><i className="fa fa-plus"></i></span>
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu>
|
||||
<OverlayTrigger placement="left" overlay={ uploadTooltip }>
|
||||
<a href="#" className="feba-btn feba-upload">
|
||||
<input type="file"
|
||||
onChange={ this.uploadFile.bind(this) }
|
||||
style={ { display: 'none' } }
|
||||
id="file-input"></input>
|
||||
<label htmlFor="file-input"> <i className="fa fa-cloud-upload"></i> </label>
|
||||
</a>
|
||||
</OverlayTrigger>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ classNames({
|
||||
'file-explorer': true,
|
||||
'toggled': sidebarStatus
|
||||
}) }>
|
||||
<SideBar searchBuckets={ this.searchBuckets.bind(this) }
|
||||
selectBucket={ this.selectBucket.bind(this) }
|
||||
clickOutside={ this.hideSidebar.bind(this) }
|
||||
showPolicy={ this.showBucketPolicy.bind(this) } />
|
||||
<div className="fe-body">
|
||||
<Dropzone>
|
||||
{ alertBox }
|
||||
<header className="fe-header-mobile hidden-lg hidden-md">
|
||||
<div id="feh-trigger" className={ 'feh-trigger ' + (classNames({
|
||||
'feht-toggled': sidebarStatus
|
||||
})) } onClick={ this.toggleSidebar.bind(this, !sidebarStatus) }>
|
||||
<div className="feht-lines">
|
||||
<div className="top"></div>
|
||||
<div className="center"></div>
|
||||
<div className="bottom"></div>
|
||||
</div>
|
||||
</div>
|
||||
<img className="mh-logo" src={ logo } alt="" />
|
||||
</header>
|
||||
<header className="fe-header">
|
||||
<Path selectPrefix={ this.selectPrefix.bind(this) } />
|
||||
{ storageUsageDetails }
|
||||
<ul className="feh-actions">
|
||||
<BrowserUpdate />
|
||||
{ loginButton }
|
||||
{ browserDropdownButton }
|
||||
</ul>
|
||||
</header>
|
||||
<div className="feb-container">
|
||||
<header className="fesl-row" data-type="folder">
|
||||
<div className="fesl-item fi-name" onClick={ this.sortObjectsByName.bind(this) } data-sort="name">
|
||||
Name
|
||||
<i className={ classNames({
|
||||
'fesli-sort': true,
|
||||
'fa': true,
|
||||
'fa-sort-alpha-desc': sortNameOrder,
|
||||
'fa-sort-alpha-asc': !sortNameOrder
|
||||
}) } />
|
||||
</div>
|
||||
<div className="fesl-item fi-size" onClick={ this.sortObjectsBySize.bind(this) } data-sort="size">
|
||||
Size
|
||||
<i className={ classNames({
|
||||
'fesli-sort': true,
|
||||
'fa': true,
|
||||
'fa-sort-amount-desc': sortSizeOrder,
|
||||
'fa-sort-amount-asc': !sortSizeOrder
|
||||
}) } />
|
||||
</div>
|
||||
<div className="fesl-item fi-modified" onClick={ this.sortObjectsByDate.bind(this) } data-sort="last-modified">
|
||||
Last Modified
|
||||
<i className={ classNames({
|
||||
'fesli-sort': true,
|
||||
'fa': true,
|
||||
'fa-sort-numeric-desc': sortDateOrder,
|
||||
'fa-sort-numeric-asc': !sortDateOrder
|
||||
}) } />
|
||||
</div>
|
||||
<div className="fesl-item fi-actions"></div>
|
||||
</header>
|
||||
</div>
|
||||
<div className="feb-container">
|
||||
<InfiniteScroll loadMore={ this.listObjects.bind(this) }
|
||||
hasMore={ istruncated }
|
||||
useWindow={ true }
|
||||
initialLoad={ false }>
|
||||
<ObjectsList dataType={ this.dataType.bind(this) }
|
||||
selectPrefix={ this.selectPrefix.bind(this) }
|
||||
showDeleteConfirmation={ this.showDeleteConfirmation.bind(this) }
|
||||
shareObject={ this.shareObject.bind(this) } />
|
||||
</InfiniteScroll>
|
||||
<div className="text-center" style={ { display: istruncated ? 'block' : 'none' } }>
|
||||
<span>Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
<UploadModal />
|
||||
{ createButton }
|
||||
<Modal className="modal-create-bucket"
|
||||
bsSize="small"
|
||||
animation={ false }
|
||||
show={ showMakeBucketModal }
|
||||
onHide={ this.hideMakeBucketModal.bind(this) }>
|
||||
<button className="close close-alt" onClick={ this.hideMakeBucketModal.bind(this) }>
|
||||
<span>×</span>
|
||||
</button>
|
||||
<ModalBody>
|
||||
<form onSubmit={ this.makeBucket.bind(this) }>
|
||||
<div className="input-group">
|
||||
<input className="ig-text"
|
||||
type="text"
|
||||
ref="makeBucketRef"
|
||||
placeholder="Bucket Name"
|
||||
autoFocus/>
|
||||
<i className="ig-helpers"></i>
|
||||
</div>
|
||||
</form>
|
||||
</ModalBody>
|
||||
</Modal>
|
||||
<Modal className="modal-about modal-dark"
|
||||
animation={ false }
|
||||
show={ showAbout }
|
||||
onHide={ this.hideAbout.bind(this) }>
|
||||
<button className="close" onClick={ this.hideAbout.bind(this) }>
|
||||
<span>×</span>
|
||||
</button>
|
||||
<div className="ma-inner">
|
||||
<div className="mai-item hidden-xs">
|
||||
<a href="https://minio.io" target="_blank"><img className="maii-logo" src={ logo } alt="" /></a>
|
||||
</div>
|
||||
<div className="mai-item">
|
||||
<ul className="maii-list">
|
||||
<li>
|
||||
<div>
|
||||
Version
|
||||
</div>
|
||||
<small>{ version }</small>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
Memory
|
||||
</div>
|
||||
<small>{ memory }</small>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
Platform
|
||||
</div>
|
||||
<small>{ platform }</small>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
Runtime
|
||||
</div>
|
||||
<small>{ runtime }</small>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
<Modal className="modal-policy"
|
||||
animation={ false }
|
||||
show={ showBucketPolicy }
|
||||
onHide={ this.hideBucketPolicy.bind(this) }>
|
||||
<ModalHeader>
|
||||
Bucket Policy (
|
||||
{ currentBucket })
|
||||
<button className="close close-alt" onClick={ this.hideBucketPolicy.bind(this) }>
|
||||
<span>×</span>
|
||||
</button>
|
||||
</ModalHeader>
|
||||
<div className="pm-body">
|
||||
<PolicyInput bucket={ currentBucket } />
|
||||
{ policies.map((policy, i) => <Policy key={ i } prefix={ policy.prefix } policy={ policy.policy } />
|
||||
) }
|
||||
</div>
|
||||
</Modal>
|
||||
<ConfirmModal show={ deleteConfirmation.show }
|
||||
icon='fa fa-exclamation-triangle mci-red'
|
||||
text='Are you sure you want to delete?'
|
||||
sub='This cannot be undone!'
|
||||
okText='Delete'
|
||||
cancelText='Cancel'
|
||||
okHandler={ this.removeObject.bind(this) }
|
||||
cancelHandler={ this.hideDeleteConfirmation.bind(this) }>
|
||||
</ConfirmModal>
|
||||
<Modal show={ shareObject.show }
|
||||
animation={ false }
|
||||
onHide={ this.hideShareObjectModal.bind(this) }
|
||||
bsSize="small">
|
||||
<ModalHeader>
|
||||
Share Object
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<div className="input-group copy-text">
|
||||
<label>
|
||||
Shareable Link
|
||||
</label>
|
||||
<input type="text"
|
||||
ref="copyTextInput"
|
||||
readOnly="readOnly"
|
||||
value={ window.location.protocol + '//' + shareObject.url }
|
||||
onClick={ this.selectTexts.bind(this) } />
|
||||
</div>
|
||||
<div className="input-group" style={ { display: web.LoggedIn() ? 'block' : 'none' } }>
|
||||
<label>
|
||||
Expires in
|
||||
</label>
|
||||
<div className="set-expire">
|
||||
<div className="set-expire-item">
|
||||
<i className="set-expire-increase" onClick={ this.handleExpireValue.bind(this, 'expireDays', 1) }></i>
|
||||
<div className="set-expire-title">
|
||||
Days
|
||||
</div>
|
||||
<div className="set-expire-value">
|
||||
<input ref="expireDays"
|
||||
type="number"
|
||||
min={ 0 }
|
||||
max={ 7 }
|
||||
defaultValue={ 0 } />
|
||||
</div>
|
||||
<i className="set-expire-decrease" onClick={ this.handleExpireValue.bind(this, 'expireDays', -1) }></i>
|
||||
</div>
|
||||
<div className="set-expire-item">
|
||||
<i className="set-expire-increase" onClick={ this.handleExpireValue.bind(this, 'expireHours', 1) }></i>
|
||||
<div className="set-expire-title">
|
||||
Hours
|
||||
</div>
|
||||
<div className="set-expire-value">
|
||||
<input ref="expireHours"
|
||||
type="number"
|
||||
min={ 0 }
|
||||
max={ 24 }
|
||||
defaultValue={ 0 } />
|
||||
</div>
|
||||
<i className="set-expire-decrease" onClick={ this.handleExpireValue.bind(this, 'expireHours', -1) }></i>
|
||||
</div>
|
||||
<div className="set-expire-item">
|
||||
<i className="set-expire-increase" onClick={ this.handleExpireValue.bind(this, 'expireMins', 1) }></i>
|
||||
<div className="set-expire-title">
|
||||
Minutes
|
||||
</div>
|
||||
<div className="set-expire-value">
|
||||
<input ref="expireMins"
|
||||
type="number"
|
||||
min={ 1 }
|
||||
max={ 60 }
|
||||
defaultValue={ 45 } />
|
||||
</div>
|
||||
<i className="set-expire-decrease" onClick={ this.handleExpireValue.bind(this, 'expireMins', -1) }></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ModalBody>
|
||||
<div className="modal-footer">
|
||||
<CopyToClipboard text={ shareObject.url } onCopy={ this.showMessage.bind(this) }>
|
||||
<button className="btn btn-success">
|
||||
Copy Link
|
||||
</button>
|
||||
</CopyToClipboard>
|
||||
<button className="btn btn-link" onClick={ this.hideShareObjectModal.bind(this) }>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
{ settingsModal }
|
||||
</Dropzone>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016, 2017 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import connect from 'react-redux/lib/components/connect'
|
||||
import Dropdown from 'react-bootstrap/lib/Dropdown'
|
||||
|
||||
let BrowserDropdown = ({fullScreenFunc, aboutFunc, settingsFunc, logoutFunc}) => {
|
||||
return (
|
||||
<li>
|
||||
<Dropdown pullRight id="top-right-menu">
|
||||
<Dropdown.Toggle noCaret>
|
||||
<i className="fa fa-reorder"></i>
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu className="dropdown-menu-right">
|
||||
<li>
|
||||
<a target="_blank" href="https://github.com/minio/miniobrowser">Github <i className="fa fa-github"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="" onClick={ fullScreenFunc }>Fullscreen <i className="fa fa-expand"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" href="https://docs.minio.io/">Documentation <i className="fa fa-book"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" href="https://slack.minio.io">Ask for help <i className="fa fa-question-circle"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="" onClick={ aboutFunc }>About <i className="fa fa-info-circle"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="" onClick={ settingsFunc }>Settings <i className="fa fa-cog"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="" onClick={ logoutFunc }>Sign Out <i className="fa fa-sign-out"></i></a>
|
||||
</li>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect(state => state)(BrowserDropdown)
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import connect from 'react-redux/lib/components/connect'
|
||||
|
||||
import Tooltip from 'react-bootstrap/lib/Tooltip'
|
||||
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'
|
||||
|
||||
let BrowserUpdate = ({latestUiVersion}) => {
|
||||
// Don't show an update if we're already updated!
|
||||
if (latestUiVersion === currentUiVersion) return ( <noscript></noscript> )
|
||||
|
||||
return (
|
||||
<li className="hidden-xs hidden-sm">
|
||||
<a href="">
|
||||
<OverlayTrigger placement="left" overlay={ <Tooltip id="tt-version-update">
|
||||
New update available. Click to refresh.
|
||||
</Tooltip> }> <i className="fa fa-refresh"></i> </OverlayTrigger>
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect(state => {
|
||||
return {
|
||||
latestUiVersion: state.latestUiVersion
|
||||
}
|
||||
})(BrowserUpdate)
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import Modal from 'react-bootstrap/lib/Modal'
|
||||
import ModalBody from 'react-bootstrap/lib/ModalBody'
|
||||
|
||||
let ConfirmModal = ({baseClass, icon, text, sub, okText, cancelText, okHandler, cancelHandler, show}) => {
|
||||
return (
|
||||
<Modal bsSize="small"
|
||||
animation={ false }
|
||||
show={ show }
|
||||
className={ "modal-confirm " + (baseClass || '') }>
|
||||
<ModalBody>
|
||||
<div className="mc-icon">
|
||||
<i className={ icon }></i>
|
||||
</div>
|
||||
<div className="mc-text">
|
||||
{ text }
|
||||
</div>
|
||||
<div className="mc-sub">
|
||||
{ sub }
|
||||
</div>
|
||||
</ModalBody>
|
||||
<div className="modal-footer">
|
||||
<button className="btn btn-danger" onClick={ okHandler }>
|
||||
{ okText }
|
||||
</button>
|
||||
<button className="btn btn-link" onClick={ cancelHandler }>
|
||||
{ cancelText }
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConfirmModal
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import ReactDropzone from 'react-dropzone'
|
||||
import * as actions from '../actions'
|
||||
|
||||
// Dropzone is a drag-and-drop element for uploading files. It will create a
|
||||
// landing zone of sorts that automatically receives the files.
|
||||
export default class Dropzone extends React.Component {
|
||||
|
||||
onDrop(files) {
|
||||
// FIXME: Currently you can upload multiple files, but only one abort
|
||||
// modal will be shown, and progress updates will only occur for one
|
||||
// file at a time. See #171.
|
||||
files.forEach(file => {
|
||||
let req = new XMLHttpRequest()
|
||||
|
||||
// Dispatch the upload.
|
||||
web.dispatch(actions.uploadFile(file, req))
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
// Overwrite the default styling from react-dropzone; otherwise it
|
||||
// won't handle child elements correctly.
|
||||
const style = {
|
||||
height: '100%',
|
||||
borderWidth: '2px',
|
||||
borderStyle: 'dashed',
|
||||
borderColor: '#fff'
|
||||
}
|
||||
const activeStyle = {
|
||||
borderColor: '#777'
|
||||
}
|
||||
const rejectStyle = {
|
||||
backgroundColor: '#ffdddd'
|
||||
}
|
||||
|
||||
// disableClick means that it won't trigger a file upload box when
|
||||
// the user clicks on a file.
|
||||
return (
|
||||
<ReactDropzone style={ style }
|
||||
activeStyle={ activeStyle }
|
||||
rejectStyle={ rejectStyle }
|
||||
disableClick={ true }
|
||||
onDrop={ this.onDrop }>
|
||||
{ this.props.children }
|
||||
</ReactDropzone>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
|
||||
let InputGroup = ({label, id, name, value, onChange, type, spellCheck, required, readonly, autoComplete, align, className}) => {
|
||||
var input = <input id={ id }
|
||||
name={ name }
|
||||
value={ value }
|
||||
onChange={ onChange }
|
||||
className="ig-text"
|
||||
type={ type }
|
||||
spellCheck={ spellCheck }
|
||||
required={ required }
|
||||
autoComplete={ autoComplete } />
|
||||
if (readonly)
|
||||
input = <input id={ id }
|
||||
name={ name }
|
||||
value={ value }
|
||||
onChange={ onChange }
|
||||
className="ig-text"
|
||||
type={ type }
|
||||
spellCheck={ spellCheck }
|
||||
required={ required }
|
||||
autoComplete={ autoComplete }
|
||||
disabled />
|
||||
return <div className={ "input-group " + align + ' ' + className }>
|
||||
{ input }
|
||||
<i className="ig-helpers"></i>
|
||||
<label className="ig-label">
|
||||
{ label }
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
|
||||
export default InputGroup
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import classNames from 'classnames'
|
||||
import logo from '../../img/logo.svg'
|
||||
import Alert from 'react-bootstrap/lib/Alert'
|
||||
import * as actions from '../actions'
|
||||
import InputGroup from '../components/InputGroup'
|
||||
|
||||
export default class Login extends React.Component {
|
||||
handleSubmit(event) {
|
||||
event.preventDefault()
|
||||
const {web, dispatch, loginRedirectPath} = this.props
|
||||
let message = ''
|
||||
if (!document.getElementById('accessKey').value) {
|
||||
message = 'Secret Key cannot be empty'
|
||||
}
|
||||
if (!document.getElementById('secretKey').value) {
|
||||
message = 'Access Key cannot be empty'
|
||||
}
|
||||
if (message) {
|
||||
dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message
|
||||
}))
|
||||
return
|
||||
}
|
||||
web.Login({
|
||||
username: document.getElementById('accessKey').value,
|
||||
password: document.getElementById('secretKey').value
|
||||
})
|
||||
.then((res) => {
|
||||
this.context.router.push(loginRedirectPath)
|
||||
})
|
||||
.catch(e => {
|
||||
dispatch(actions.setLoginError())
|
||||
dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message: e.message
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
const {dispatch} = this.props
|
||||
// Clear out any stale message in the alert of previous page
|
||||
dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message: ''
|
||||
}))
|
||||
document.body.classList.add('is-guest')
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.body.classList.remove('is-guest')
|
||||
}
|
||||
|
||||
hideAlert() {
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.hideAlert())
|
||||
}
|
||||
|
||||
render() {
|
||||
const {alert} = this.props
|
||||
let alertBox = <Alert className={ 'alert animated ' + (alert.show ? 'fadeInDown' : 'fadeOutUp') } bsStyle={ alert.type } onDismiss={ this.hideAlert.bind(this) }>
|
||||
<div className='text-center'>
|
||||
{ alert.message }
|
||||
</div>
|
||||
</Alert>
|
||||
// Make sure you don't show a fading out alert box on the initial web-page load.
|
||||
if (!alert.message)
|
||||
alertBox = ''
|
||||
return (
|
||||
<div className="login">
|
||||
{ alertBox }
|
||||
<div className="l-wrap">
|
||||
<form onSubmit={ this.handleSubmit.bind(this) }>
|
||||
<input name="fixBrowser"
|
||||
autoComplete="username"
|
||||
type="text"
|
||||
style={ { display: 'none' } } />
|
||||
<InputGroup className="ig-dark"
|
||||
label="Access Key"
|
||||
id="accessKey"
|
||||
name="username"
|
||||
type="text"
|
||||
spellCheck="false"
|
||||
required="required"
|
||||
autoComplete="username">
|
||||
</InputGroup>
|
||||
<input type="text" autoComplete="new-password" style={ { display: 'none' } } />
|
||||
<InputGroup className="ig-dark"
|
||||
label="Secret Key"
|
||||
id="secretKey"
|
||||
name="password"
|
||||
type="password"
|
||||
spellCheck="false"
|
||||
required="required"
|
||||
autoComplete="new-password">
|
||||
</InputGroup>
|
||||
<button className="lw-btn" type="submit">
|
||||
<i className="fa fa-sign-in"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="l-footer">
|
||||
<a className="lf-logo" href=""><img src={ logo } alt="" /></a>
|
||||
<div className="lf-server">
|
||||
{ window.location.host }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Login.contextTypes = {
|
||||
router: React.PropTypes.object.isRequired
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import Moment from 'moment'
|
||||
import humanize from 'humanize'
|
||||
import connect from 'react-redux/lib/components/connect'
|
||||
import Dropdown from 'react-bootstrap/lib/Dropdown'
|
||||
|
||||
|
||||
let ObjectsList = ({objects, currentPath, selectPrefix, dataType, showDeleteConfirmation, shareObject, loadPath}) => {
|
||||
const list = objects.map((object, i) => {
|
||||
let size = object.name.endsWith('/') ? '-' : humanize.filesize(object.size)
|
||||
let lastModified = object.name.endsWith('/') ? '-' : Moment(object.lastModified).format('lll')
|
||||
let loadingClass = loadPath === `${currentPath}${object.name}` ? 'fesl-loading' : ''
|
||||
let actionButtons = ''
|
||||
let deleteButton = ''
|
||||
if (web.LoggedIn())
|
||||
deleteButton = <a href="" className="fiad-action" onClick={ (e) => showDeleteConfirmation(e, `${currentPath}${object.name}`) }><i className="fa fa-trash"></i></a>
|
||||
if (!object.name.endsWith('/')) {
|
||||
actionButtons = <Dropdown id="fia-dropdown">
|
||||
<Dropdown.Toggle noCaret className="fia-toggle"></Dropdown.Toggle>
|
||||
<Dropdown.Menu>
|
||||
<a href="" className="fiad-action" onClick={ (e) => shareObject(e, `${currentPath}${object.name}`) }><i className="fa fa-copy"></i></a>
|
||||
{ deleteButton }
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
}
|
||||
return (
|
||||
<div key={ i } className={ "fesl-row " + loadingClass } data-type={ dataType(object.name, object.contentType) }>
|
||||
<div className="fesl-item fi-name">
|
||||
<a href="" onClick={ (e) => selectPrefix(e, `${currentPath}${object.name}`) }>
|
||||
{ object.name }
|
||||
</a>
|
||||
</div>
|
||||
<div className="fesl-item fi-size">
|
||||
{ size }
|
||||
</div>
|
||||
<div className="fesl-item fi-modified">
|
||||
{ lastModified }
|
||||
</div>
|
||||
<div className="fesl-item fi-actions">
|
||||
{ actionButtons }
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
return (
|
||||
<div>
|
||||
{ list }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Subscribe it to state changes.
|
||||
export default connect(state => {
|
||||
return {
|
||||
objects: state.objects,
|
||||
currentPath: state.currentPath,
|
||||
loadPath: state.loadPath
|
||||
}
|
||||
})(ObjectsList)
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import connect from 'react-redux/lib/components/connect'
|
||||
|
||||
let Path = ({currentBucket, currentPath, selectPrefix}) => {
|
||||
let dirPath = []
|
||||
let path = ''
|
||||
if (currentPath) {
|
||||
path = currentPath.split('/').map((dir, i) => {
|
||||
dirPath.push(dir)
|
||||
let dirPath_ = dirPath.join('/') + '/'
|
||||
return <span key={ i }><a href="" onClick={ (e) => selectPrefix(e, dirPath_) }>{ dir }</a></span>
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<h2><span className="main"><a onClick={ (e) => selectPrefix(e, '') } href="">{ currentBucket }</a></span>{ path }</h2>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect(state => {
|
||||
return {
|
||||
currentBucket: state.currentBucket,
|
||||
currentPath: state.currentPath
|
||||
}
|
||||
})(Path)
|
||||
@@ -0,0 +1,80 @@
|
||||
import { READ_ONLY, WRITE_ONLY, READ_WRITE } from '../constants'
|
||||
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import connect from 'react-redux/lib/components/connect'
|
||||
import classnames from 'classnames'
|
||||
import * as actions from '../actions'
|
||||
|
||||
class Policy extends Component {
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
this.state = {}
|
||||
}
|
||||
|
||||
handlePolicyChange(e) {
|
||||
this.setState({
|
||||
policy: {
|
||||
policy: e.target.value
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
removePolicy(e) {
|
||||
e.preventDefault()
|
||||
const {dispatch, currentBucket, prefix} = this.props
|
||||
let newPrefix = prefix.replace(currentBucket + '/', '')
|
||||
newPrefix = newPrefix.replace('*', '')
|
||||
web.SetBucketPolicy({
|
||||
bucketName: currentBucket,
|
||||
prefix: newPrefix,
|
||||
policy: 'none'
|
||||
})
|
||||
.then(() => {
|
||||
dispatch(actions.setPolicies(this.props.policies.filter(policy => policy.prefix != prefix)))
|
||||
})
|
||||
.catch(e => dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message: e.message,
|
||||
})))
|
||||
}
|
||||
|
||||
render() {
|
||||
const {policy, prefix, currentBucket} = this.props
|
||||
let newPrefix = prefix.replace(currentBucket + '/', '')
|
||||
newPrefix = newPrefix.replace('*', '')
|
||||
|
||||
if (!newPrefix)
|
||||
newPrefix = '*'
|
||||
|
||||
return (
|
||||
<div className="pmb-list">
|
||||
<div className="pmbl-item">
|
||||
{ newPrefix }
|
||||
</div>
|
||||
<div className="pmbl-item">
|
||||
<select className="form-control"
|
||||
disabled
|
||||
value={ policy }
|
||||
onChange={ this.handlePolicyChange.bind(this) }>
|
||||
<option value={ READ_ONLY }>
|
||||
Read Only
|
||||
</option>
|
||||
<option value={ WRITE_ONLY }>
|
||||
Write Only
|
||||
</option>
|
||||
<option value={ READ_WRITE }>
|
||||
Read and Write
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="pmbl-item">
|
||||
<button className="btn btn-block btn-danger" onClick={ this.removePolicy.bind(this) }>
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(state => state)(Policy)
|
||||
@@ -0,0 +1,83 @@
|
||||
import { READ_ONLY, WRITE_ONLY, READ_WRITE } from '../constants'
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import connect from 'react-redux/lib/components/connect'
|
||||
import classnames from 'classnames'
|
||||
import * as actions from '../actions'
|
||||
|
||||
class PolicyInput extends Component {
|
||||
componentDidMount() {
|
||||
const {web, dispatch} = this.props
|
||||
web.ListAllBucketPolicies({
|
||||
bucketName: this.props.currentBucket
|
||||
}).then(res => {
|
||||
let policies = res.policies
|
||||
if (policies) dispatch(actions.setPolicies(policies))
|
||||
}).catch(err => {
|
||||
dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message: err.message
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.setPolicies([]))
|
||||
}
|
||||
|
||||
handlePolicySubmit(e) {
|
||||
e.preventDefault()
|
||||
const {web, dispatch} = this.props
|
||||
|
||||
web.SetBucketPolicy({
|
||||
bucketName: this.props.currentBucket,
|
||||
prefix: this.prefix.value,
|
||||
policy: this.policy.value
|
||||
})
|
||||
.then(() => {
|
||||
dispatch(actions.setPolicies([{
|
||||
policy: this.policy.value,
|
||||
prefix: this.prefix.value + '*',
|
||||
}, ...this.props.policies]))
|
||||
this.prefix.value = ''
|
||||
})
|
||||
.catch(e => dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message: e.message,
|
||||
})))
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<header className="pmb-list">
|
||||
<div className="pmbl-item">
|
||||
<input type="text"
|
||||
ref={ prefix => this.prefix = prefix }
|
||||
className="form-control"
|
||||
placeholder="Prefix"
|
||||
editable={ true } />
|
||||
</div>
|
||||
<div className="pmbl-item">
|
||||
<select ref={ policy => this.policy = policy } className="form-control">
|
||||
<option value={ READ_ONLY }>
|
||||
Read Only
|
||||
</option>
|
||||
<option value={ WRITE_ONLY }>
|
||||
Write Only
|
||||
</option>
|
||||
<option value={ READ_WRITE }>
|
||||
Read and Write
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="pmbl-item">
|
||||
<button className="btn btn-block btn-primary" onClick={ this.handlePolicySubmit.bind(this) }>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(state => state)(PolicyInput)
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import connect from 'react-redux/lib/components/connect'
|
||||
import * as actions from '../actions'
|
||||
|
||||
import Tooltip from 'react-bootstrap/lib/Tooltip'
|
||||
import Modal from 'react-bootstrap/lib/Modal'
|
||||
import ModalBody from 'react-bootstrap/lib/ModalBody'
|
||||
import ModalHeader from 'react-bootstrap/lib/ModalHeader'
|
||||
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'
|
||||
import InputGroup from './InputGroup'
|
||||
|
||||
class SettingsModal extends React.Component {
|
||||
|
||||
// When the settings are shown, it loads the access key and secret key.
|
||||
componentWillMount() {
|
||||
const {web, dispatch} = this.props
|
||||
const {serverInfo} = this.props
|
||||
|
||||
let accessKeyEnv = ''
|
||||
let secretKeyEnv = ''
|
||||
// Check environment variables first. They may or may not have been
|
||||
// loaded already; they load in Browse#componentDidMount.
|
||||
if (serverInfo.envVars) {
|
||||
serverInfo.envVars.forEach(envVar => {
|
||||
let keyVal = envVar.split('=')
|
||||
if (keyVal[0] == 'MINIO_ACCESS_KEY') {
|
||||
accessKeyEnv = keyVal[1]
|
||||
} else if (keyVal[0] == 'MINIO_SECRET_KEY') {
|
||||
secretKeyEnv = keyVal[1]
|
||||
}
|
||||
})
|
||||
}
|
||||
if (accessKeyEnv != '' || secretKeyEnv != '') {
|
||||
dispatch(actions.setSettings({
|
||||
accessKey: accessKeyEnv,
|
||||
secretKey: secretKeyEnv,
|
||||
keysReadOnly: true
|
||||
}))
|
||||
} else {
|
||||
web.GetAuth()
|
||||
.then(data => {
|
||||
dispatch(actions.setSettings({
|
||||
accessKey: data.accessKey,
|
||||
secretKey: data.secretKey
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// When they are re-hidden, the keys are unloaded from memory.
|
||||
componentWillUnmount() {
|
||||
const {dispatch} = this.props
|
||||
|
||||
dispatch(actions.setSettings({
|
||||
accessKey: '',
|
||||
secretKey: '',
|
||||
secretKeyVisible: false
|
||||
}))
|
||||
dispatch(actions.hideSettings())
|
||||
}
|
||||
|
||||
// Handle field changes from inside the modal.
|
||||
accessKeyChange(e) {
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.setSettings({
|
||||
accessKey: e.target.value
|
||||
}))
|
||||
}
|
||||
|
||||
secretKeyChange(e) {
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.setSettings({
|
||||
secretKey: e.target.value
|
||||
}))
|
||||
}
|
||||
|
||||
secretKeyVisible(secretKeyVisible) {
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.setSettings({
|
||||
secretKeyVisible
|
||||
}))
|
||||
}
|
||||
|
||||
// Save the auth params and set them.
|
||||
setAuth(e) {
|
||||
e.preventDefault()
|
||||
const {web, dispatch} = this.props
|
||||
|
||||
let accessKey = document.getElementById('accessKey').value
|
||||
let secretKey = document.getElementById('secretKey').value
|
||||
web.SetAuth({
|
||||
accessKey,
|
||||
secretKey
|
||||
})
|
||||
.then(data => {
|
||||
dispatch(actions.setSettings({
|
||||
accessKey: '',
|
||||
secretKey: '',
|
||||
secretKeyVisible: false
|
||||
}))
|
||||
dispatch(actions.hideSettings())
|
||||
dispatch(actions.showAlert({
|
||||
type: 'success',
|
||||
message: 'Changed credentials'
|
||||
}))
|
||||
})
|
||||
.catch(err => {
|
||||
dispatch(actions.setSettings({
|
||||
accessKey: '',
|
||||
secretKey: '',
|
||||
secretKeyVisible: false
|
||||
}))
|
||||
dispatch(actions.hideSettings())
|
||||
dispatch(actions.showAlert({
|
||||
type: 'danger',
|
||||
message: err.message
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
generateAuth(e) {
|
||||
e.preventDefault()
|
||||
const {dispatch} = this.props
|
||||
|
||||
web.GenerateAuth()
|
||||
.then(data => {
|
||||
dispatch(actions.setSettings({
|
||||
secretKeyVisible: true
|
||||
}))
|
||||
dispatch(actions.setSettings({
|
||||
accessKey: data.accessKey,
|
||||
secretKey: data.secretKey
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
hideSettings(e) {
|
||||
e.preventDefault()
|
||||
|
||||
const {dispatch} = this.props
|
||||
dispatch(actions.hideSettings())
|
||||
}
|
||||
|
||||
render() {
|
||||
let {settings} = this.props
|
||||
|
||||
return (
|
||||
<Modal bsSize="sm" animation={ false } show={ true }>
|
||||
<ModalHeader>
|
||||
Change Password
|
||||
</ModalHeader>
|
||||
<ModalBody className="m-t-20">
|
||||
<InputGroup value={ settings.accessKey }
|
||||
onChange={ this.accessKeyChange.bind(this) }
|
||||
id="accessKey"
|
||||
label="Access Key"
|
||||
name="accesskey"
|
||||
type="text"
|
||||
spellCheck="false"
|
||||
required="required"
|
||||
autoComplete="false"
|
||||
align="ig-left"
|
||||
readonly={ settings.keysReadOnly }></InputGroup>
|
||||
<i onClick={ this.secretKeyVisible.bind(this, !settings.secretKeyVisible) } className={ "toggle-password fa fa-eye " + (settings.secretKeyVisible ? "toggled" : "") } />
|
||||
<InputGroup value={ settings.secretKey }
|
||||
onChange={ this.secretKeyChange.bind(this) }
|
||||
id="secretKey"
|
||||
label="Secret Key"
|
||||
name="accesskey"
|
||||
type={ settings.secretKeyVisible ? "text" : "password" }
|
||||
spellCheck="false"
|
||||
required="required"
|
||||
autoComplete="false"
|
||||
align="ig-left"
|
||||
readonly={ settings.keysReadOnly }></InputGroup>
|
||||
</ModalBody>
|
||||
<div className="modal-footer">
|
||||
<button className={ "btn btn-primary " + (settings.keysReadOnly ? "hidden" : "") } onClick={ this.generateAuth.bind(this) }>
|
||||
Generate
|
||||
</button>
|
||||
<button href="" className={ "btn btn-success " + (settings.keysReadOnly ? "hidden" : "") } onClick={ this.setAuth.bind(this) }>
|
||||
Update
|
||||
</button>
|
||||
<button href="" className="btn btn-link" onClick={ this.hideSettings.bind(this) }>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(state => {
|
||||
return {
|
||||
web: state.web,
|
||||
settings: state.settings,
|
||||
serverInfo: state.serverInfo
|
||||
}
|
||||
})(SettingsModal)
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import classNames from 'classnames'
|
||||
import ClickOutHandler from 'react-onclickout'
|
||||
import Scrollbars from 'react-custom-scrollbars/lib/Scrollbars'
|
||||
import connect from 'react-redux/lib/components/connect'
|
||||
|
||||
import logo from '../../img/logo.svg'
|
||||
|
||||
let SideBar = ({visibleBuckets, loadBucket, currentBucket, selectBucket, searchBuckets, sidebarStatus, clickOutside, showPolicy}) => {
|
||||
|
||||
const list = visibleBuckets.map((bucket, i) => {
|
||||
return <li className={ classNames({
|
||||
'active': bucket === currentBucket
|
||||
}) } key={ i } onClick={ (e) => selectBucket(e, bucket) }>
|
||||
<a href="" className={ classNames({
|
||||
'fesli-loading': bucket === loadBucket
|
||||
}) }>
|
||||
{ bucket }
|
||||
</a>
|
||||
<i className="fesli-trigger" onClick={ showPolicy }></i>
|
||||
</li>
|
||||
})
|
||||
|
||||
return (
|
||||
<ClickOutHandler onClickOut={ clickOutside }>
|
||||
<div className={ classNames({
|
||||
'fe-sidebar': true,
|
||||
'toggled': sidebarStatus
|
||||
}) }>
|
||||
<div className="fes-header clearfix hidden-sm hidden-xs">
|
||||
<img src={ logo } alt="" />
|
||||
<h2>Minio Browser</h2>
|
||||
</div>
|
||||
<div className="fes-list">
|
||||
<div className="input-group ig-dark ig-left ig-search" style={ { display: web.LoggedIn() ? 'block' : 'none' } }>
|
||||
<input className="ig-text"
|
||||
type="text"
|
||||
onChange={ searchBuckets }
|
||||
placeholder="Search Buckets..." />
|
||||
<i className="ig-helpers"></i>
|
||||
</div>
|
||||
<div className="fesl-inner">
|
||||
<Scrollbars renderScrollbarVertical={ props => <div className="scrollbar-vertical" /> }>
|
||||
<ul>
|
||||
{ list }
|
||||
</ul>
|
||||
</Scrollbars>
|
||||
</div>
|
||||
</div>
|
||||
<div className="fes-host">
|
||||
<i className="fa fa-globe"></i>
|
||||
<a href="/">
|
||||
{ window.location.host }
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</ClickOutHandler>
|
||||
)
|
||||
}
|
||||
|
||||
// Subscribe it to state changes that affect only the sidebar.
|
||||
export default connect(state => {
|
||||
return {
|
||||
visibleBuckets: state.visibleBuckets,
|
||||
loadBucket: state.loadBucket,
|
||||
currentBucket: state.currentBucket,
|
||||
sidebarStatus: state.sidebarStatus
|
||||
}
|
||||
})(SideBar)
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import humanize from 'humanize'
|
||||
import classNames from 'classnames'
|
||||
import connect from 'react-redux/lib/components/connect'
|
||||
|
||||
import ProgressBar from 'react-bootstrap/lib/ProgressBar'
|
||||
import ConfirmModal from './ConfirmModal'
|
||||
|
||||
import * as actions from '../actions'
|
||||
|
||||
// UploadModal is a modal that handles multiple file uploads.
|
||||
// During the upload, it displays a progress bar, and can transform into an
|
||||
// abort modal if the user decides to abort the uploads.
|
||||
class UploadModal extends React.Component {
|
||||
|
||||
// Abort all the current uploads.
|
||||
abortUploads(e) {
|
||||
e.preventDefault()
|
||||
const {dispatch, uploads} = this.props
|
||||
|
||||
for (var slug in uploads) {
|
||||
let upload = uploads[slug]
|
||||
upload.xhr.abort()
|
||||
dispatch(actions.stopUpload({
|
||||
slug
|
||||
}))
|
||||
}
|
||||
|
||||
this.hideAbort(e)
|
||||
}
|
||||
|
||||
// Show the abort modal instead of the progress modal.
|
||||
showAbort(e) {
|
||||
e.preventDefault()
|
||||
const {dispatch} = this.props
|
||||
|
||||
dispatch(actions.setShowAbortModal(true))
|
||||
}
|
||||
|
||||
// Show the progress modal instead of the abort modal.
|
||||
hideAbort(e) {
|
||||
e.preventDefault()
|
||||
const {dispatch} = this.props
|
||||
|
||||
dispatch(actions.setShowAbortModal(false))
|
||||
}
|
||||
|
||||
render() {
|
||||
const {uploads, showAbortModal} = this.props
|
||||
|
||||
// Show the abort modal.
|
||||
if (showAbortModal) {
|
||||
let baseClass = classNames({
|
||||
'abort-upload': true
|
||||
})
|
||||
let okIcon = classNames({
|
||||
'fa': true,
|
||||
'fa-times': true
|
||||
})
|
||||
let cancelIcon = classNames({
|
||||
'fa': true,
|
||||
'fa-cloud-upload': true
|
||||
})
|
||||
|
||||
return (
|
||||
<ConfirmModal show={ true }
|
||||
baseClass={ baseClass }
|
||||
text='Abort uploads in progress?'
|
||||
icon='fa fa-info-circle mci-amber'
|
||||
sub='This cannot be undone!'
|
||||
okText='Abort'
|
||||
okIcon={ okIcon }
|
||||
cancelText='Upload'
|
||||
cancelIcon={ cancelIcon }
|
||||
okHandler={ this.abortUploads.bind(this) }
|
||||
cancelHandler={ this.hideAbort.bind(this) }>
|
||||
</ConfirmModal>
|
||||
)
|
||||
}
|
||||
|
||||
// If we don't have any files uploading, don't show anything.
|
||||
let numberUploading = Object.keys(uploads).length
|
||||
if (numberUploading == 0)
|
||||
return ( <noscript></noscript> )
|
||||
|
||||
let totalLoaded = 0
|
||||
let totalSize = 0
|
||||
|
||||
// Iterate over each upload, adding together the total size and that
|
||||
// which has been uploaded.
|
||||
for (var slug in uploads) {
|
||||
let upload = uploads[slug]
|
||||
totalLoaded += upload.loaded
|
||||
totalSize += upload.size
|
||||
}
|
||||
|
||||
let percent = (totalLoaded / totalSize) * 100
|
||||
|
||||
// If more than one: "Uploading files (5)..."
|
||||
// If only one: "Uploading myfile.txt..."
|
||||
let text = 'Uploading ' + (numberUploading == 1 ? `'${uploads[Object.keys(uploads)[0]].name}'` : `files (${numberUploading})`) + '...'
|
||||
|
||||
return (
|
||||
<div className="alert alert-info progress animated fadeInUp ">
|
||||
<button type="button" className="close" onClick={ this.showAbort.bind(this) }>
|
||||
<span>×</span>
|
||||
</button>
|
||||
<div className="text-center">
|
||||
<small>{ text }</small>
|
||||
</div>
|
||||
<ProgressBar now={ percent } />
|
||||
<div className="text-center">
|
||||
<small>{ humanize.filesize(totalLoaded) } ({ percent.toFixed(2) } %)</small>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(state => {
|
||||
return {
|
||||
uploads: state.uploads,
|
||||
showAbortModal: state.showAbortModal
|
||||
}
|
||||
})(UploadModal)
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
import React from 'react'
|
||||
import ReactTestUtils, {renderIntoDocument} from 'react-addons-test-utils'
|
||||
|
||||
import expect from 'expect'
|
||||
import Login from '../Login'
|
||||
|
||||
describe('Login', () => {
|
||||
it('it should have empty credentials', () => {
|
||||
const alert = {
|
||||
show: false
|
||||
}
|
||||
const dispatch = () => {}
|
||||
let loginComponent = renderIntoDocument(<Login alert={alert} dispatch={dispatch} />)
|
||||
const accessKey = document.getElementById('accessKey')
|
||||
const secretKey = document.getElementById('secretKey')
|
||||
// Validate default value.
|
||||
expect(accessKey.value).toEqual('')
|
||||
expect(secretKey.value).toEqual('')
|
||||
})
|
||||
it('it should set accessKey and secretKey', () => {
|
||||
const alert = {
|
||||
show: false
|
||||
}
|
||||
const dispatch = () => {}
|
||||
let loginComponent = renderIntoDocument(<Login alert={alert} dispatch={dispatch} />)
|
||||
let accessKey = loginComponent.refs.accessKey
|
||||
let secretKey = loginComponent.refs.secretKey
|
||||
accessKey.value = 'demo-username'
|
||||
secretKey.value = 'demo-password'
|
||||
ReactTestUtils.Simulate.change(accessKey)
|
||||
ReactTestUtils.Simulate.change(secretKey)
|
||||
// Validate if the change has occurred.
|
||||
expect(loginComponent.refs.accessKey.value).toEqual('demo-username')
|
||||
expect(loginComponent.refs.secretKey.value).toEqual('demo-password')
|
||||
})
|
||||
});
|
||||
*/
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// File for all the browser constants.
|
||||
|
||||
// minioBrowserPrefix absolute path.
|
||||
export const minioBrowserPrefix = '/minio'
|
||||
export const READ_ONLY = 'readonly'
|
||||
export const WRITE_ONLY = 'writeonly'
|
||||
export const READ_WRITE = 'readwrite'
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import SuperAgent from 'superagent-es6-promise';
|
||||
import url from 'url'
|
||||
import Moment from 'moment'
|
||||
|
||||
export default class JSONrpc {
|
||||
constructor(params) {
|
||||
this.endpoint = params.endpoint
|
||||
this.namespace = params.namespace
|
||||
this.version = '2.0';
|
||||
const parsedUrl = url.parse(this.endpoint)
|
||||
this.host = parsedUrl.hostname
|
||||
this.path = parsedUrl.path
|
||||
this.port = parsedUrl.port
|
||||
|
||||
switch (parsedUrl.protocol) {
|
||||
case 'http:': {
|
||||
this.scheme = 'http'
|
||||
if (parsedUrl.port === 0) {
|
||||
this.port = 80
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'https:': {
|
||||
this.scheme = 'https'
|
||||
if (parsedUrl.port === 0) {
|
||||
this.port = 443
|
||||
}
|
||||
break
|
||||
}
|
||||
default: {
|
||||
throw new Error('Unknown protocol: ' + parsedUrl.protocol)
|
||||
}
|
||||
}
|
||||
}
|
||||
// call('Get', {id: NN, params: [...]}, function() {})
|
||||
call(method, options, token) {
|
||||
if (!options) {
|
||||
options = {}
|
||||
}
|
||||
if (!options.id) {
|
||||
options.id = 1;
|
||||
}
|
||||
if (!options.params) {
|
||||
options.params = {};
|
||||
}
|
||||
const dataObj = {
|
||||
id: options.id,
|
||||
jsonrpc: this.version,
|
||||
params: options.params ? options.params : {},
|
||||
method: this.namespace ? this.namespace + '.' + method : method
|
||||
}
|
||||
let requestParams = {
|
||||
host: this.host,
|
||||
port: this.port,
|
||||
path: this.path,
|
||||
scheme: this.scheme,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-amz-date': Moment().utc().format('YYYYMMDDTHHmmss') + 'Z'
|
||||
}
|
||||
}
|
||||
|
||||
if (token) {
|
||||
requestParams.headers.Authorization = 'Bearer ' + token
|
||||
}
|
||||
|
||||
let req = SuperAgent.post(this.endpoint)
|
||||
for (let key in requestParams.headers) {
|
||||
req.set(key, requestParams.headers[key])
|
||||
}
|
||||
// req.set('Access-Control-Allow-Origin', 'http://localhost:8080')
|
||||
return req.send(JSON.stringify(dataObj)).then(res => res)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import mimedb from 'mime-types'
|
||||
|
||||
const isFolder = (name, contentType) => {
|
||||
if (name.endsWith('/')) return true
|
||||
return false
|
||||
}
|
||||
|
||||
const isPdf = (name, contentType) => {
|
||||
if (contentType === 'application/pdf') return true
|
||||
return false
|
||||
}
|
||||
|
||||
const isZip = (name, contentType) => {
|
||||
if (!contentType || !contentType.includes('/')) return false
|
||||
if (contentType.split('/')[1].includes('zip')) return true
|
||||
return false
|
||||
}
|
||||
|
||||
const isCode = (name, contentType) => {
|
||||
const codeExt = ['c', 'cpp', 'go', 'py', 'java', 'rb', 'js', 'pl', 'fs',
|
||||
'php', 'css', 'less', 'scss', 'coffee', 'net', 'html',
|
||||
'rs', 'exs', 'scala', 'hs', 'clj', 'el', 'scm', 'lisp',
|
||||
'asp', 'aspx']
|
||||
const ext = name.split('.').reverse()[0]
|
||||
for (var i in codeExt) {
|
||||
if (ext === codeExt[i]) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const isExcel = (name, contentType) => {
|
||||
if (!contentType || !contentType.includes('/')) return false
|
||||
const types = ['excel', 'spreadsheet']
|
||||
const subType = contentType.split('/')[1]
|
||||
for (var i in types) {
|
||||
if (subType.includes(types[i])) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const isDoc = (name, contentType) => {
|
||||
if (!contentType || !contentType.includes('/')) return false
|
||||
const types = ['word', '.document']
|
||||
const subType = contentType.split('/')[1]
|
||||
for (var i in types) {
|
||||
if (subType.includes(types[i])) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const isPresentation = (name, contentType) => {
|
||||
if (!contentType || !contentType.includes('/')) return false
|
||||
var types = ['powerpoint', 'presentation']
|
||||
const subType = contentType.split('/')[1]
|
||||
for (var i in types) {
|
||||
if (subType.includes(types[i])) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const typeToIcon = (type) => {
|
||||
return (name, contentType) => {
|
||||
if (!contentType || !contentType.includes('/')) return false
|
||||
if (contentType.split('/')[0] === type) return true
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export const getDataType = (name, contentType) => {
|
||||
if (contentType === "") {
|
||||
contentType = mimedb.lookup(name) || 'application/octet-stream'
|
||||
}
|
||||
const check = [
|
||||
['folder', isFolder],
|
||||
['code', isCode],
|
||||
['audio', typeToIcon('audio')],
|
||||
['image', typeToIcon('image')],
|
||||
['video', typeToIcon('video')],
|
||||
['text', typeToIcon('text')],
|
||||
['pdf', isPdf],
|
||||
['zip', isZip],
|
||||
['excel', isExcel],
|
||||
['doc', isDoc],
|
||||
['presentation', isPresentation]
|
||||
]
|
||||
for (var i in check) {
|
||||
if (check[i][1](name, contentType)) return check[i][0]
|
||||
}
|
||||
return 'other'
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as actions from './actions'
|
||||
import { minioBrowserPrefix } from './constants'
|
||||
|
||||
export default (state = {
|
||||
buckets: [],
|
||||
visibleBuckets: [],
|
||||
objects: [],
|
||||
istruncated: true,
|
||||
storageInfo: {},
|
||||
serverInfo: {},
|
||||
currentBucket: '',
|
||||
currentPath: '',
|
||||
showMakeBucketModal: false,
|
||||
uploads: {},
|
||||
alert: {
|
||||
show: false,
|
||||
type: 'danger',
|
||||
message: ''
|
||||
},
|
||||
loginError: false,
|
||||
sortNameOrder: false,
|
||||
sortSizeOrder: false,
|
||||
sortDateOrder: false,
|
||||
latestUiVersion: currentUiVersion,
|
||||
sideBarActive: false,
|
||||
loginRedirectPath: minioBrowserPrefix,
|
||||
settings: {
|
||||
accessKey: '',
|
||||
secretKey: '',
|
||||
secretKeyVisible: false
|
||||
},
|
||||
showSettings: false,
|
||||
policies: [],
|
||||
deleteConfirmation: {
|
||||
object: '',
|
||||
show: false
|
||||
},
|
||||
shareObject: {
|
||||
show: false,
|
||||
url: '',
|
||||
expiry: 604800
|
||||
},
|
||||
prefixWritable: false
|
||||
}, action) => {
|
||||
let newState = Object.assign({}, state)
|
||||
switch (action.type) {
|
||||
case actions.SET_WEB:
|
||||
newState.web = action.web
|
||||
break
|
||||
case actions.SET_BUCKETS:
|
||||
newState.buckets = action.buckets
|
||||
break
|
||||
case actions.ADD_BUCKET:
|
||||
newState.buckets = [action.bucket, ...newState.buckets]
|
||||
newState.visibleBuckets = [action.bucket, ...newState.visibleBuckets]
|
||||
break
|
||||
case actions.SET_VISIBLE_BUCKETS:
|
||||
newState.visibleBuckets = action.visibleBuckets
|
||||
break
|
||||
case actions.SET_CURRENT_BUCKET:
|
||||
newState.currentBucket = action.currentBucket
|
||||
break
|
||||
case actions.SET_OBJECTS:
|
||||
if (!action.objects.length) {
|
||||
newState.objects = []
|
||||
newState.marker = ""
|
||||
newState.istruncated = action.istruncated
|
||||
} else {
|
||||
newState.objects = [...newState.objects, ...action.objects]
|
||||
newState.marker = action.marker
|
||||
newState.istruncated = action.istruncated
|
||||
}
|
||||
break
|
||||
case actions.SET_CURRENT_PATH:
|
||||
newState.currentPath = action.currentPath
|
||||
break
|
||||
case actions.SET_STORAGE_INFO:
|
||||
newState.storageInfo = action.storageInfo
|
||||
break
|
||||
case actions.SET_SERVER_INFO:
|
||||
newState.serverInfo = action.serverInfo
|
||||
break
|
||||
case actions.SHOW_MAKEBUCKET_MODAL:
|
||||
newState.showMakeBucketModal = action.showMakeBucketModal
|
||||
break
|
||||
case actions.UPLOAD_PROGRESS:
|
||||
newState.uploads = Object.assign({}, newState.uploads)
|
||||
newState.uploads[action.slug].loaded = action.loaded
|
||||
break
|
||||
case actions.ADD_UPLOAD:
|
||||
newState.uploads = Object.assign({}, newState.uploads, {
|
||||
[action.slug]: {
|
||||
loaded: 0,
|
||||
size: action.size,
|
||||
xhr: action.xhr,
|
||||
name: action.name
|
||||
}
|
||||
})
|
||||
break
|
||||
case actions.STOP_UPLOAD:
|
||||
newState.uploads = Object.assign({}, newState.uploads)
|
||||
delete newState.uploads[action.slug]
|
||||
break
|
||||
case actions.SET_ALERT:
|
||||
if (newState.alert.alertTimeout) clearTimeout(newState.alert.alertTimeout)
|
||||
if (!action.alert.show) {
|
||||
newState.alert = Object.assign({}, newState.alert, {
|
||||
show: false
|
||||
})
|
||||
} else {
|
||||
newState.alert = action.alert
|
||||
}
|
||||
break
|
||||
case actions.SET_LOGIN_ERROR:
|
||||
newState.loginError = true
|
||||
break
|
||||
case actions.SET_SHOW_ABORT_MODAL:
|
||||
newState.showAbortModal = action.showAbortModal
|
||||
break
|
||||
case actions.SHOW_ABOUT:
|
||||
newState.showAbout = action.showAbout
|
||||
break
|
||||
case actions.SET_SORT_NAME_ORDER:
|
||||
newState.sortNameOrder = action.sortNameOrder
|
||||
break
|
||||
case actions.SET_SORT_SIZE_ORDER:
|
||||
newState.sortSizeOrder = action.sortSizeOrder
|
||||
break
|
||||
case actions.SET_SORT_DATE_ORDER:
|
||||
newState.sortDateOrder = action.sortDateOrder
|
||||
break
|
||||
case actions.SET_LATEST_UI_VERSION:
|
||||
newState.latestUiVersion = action.latestUiVersion
|
||||
break
|
||||
case actions.SET_SIDEBAR_STATUS:
|
||||
newState.sidebarStatus = action.sidebarStatus
|
||||
break
|
||||
case actions.SET_LOGIN_REDIRECT_PATH:
|
||||
newState.loginRedirectPath = action.path
|
||||
case actions.SET_LOAD_BUCKET:
|
||||
newState.loadBucket = action.loadBucket
|
||||
break
|
||||
case actions.SET_LOAD_PATH:
|
||||
newState.loadPath = action.loadPath
|
||||
break
|
||||
case actions.SHOW_SETTINGS:
|
||||
newState.showSettings = action.showSettings
|
||||
break
|
||||
case actions.SET_SETTINGS:
|
||||
newState.settings = Object.assign({}, newState.settings, action.settings)
|
||||
break
|
||||
case actions.SHOW_BUCKET_POLICY:
|
||||
newState.showBucketPolicy = action.showBucketPolicy
|
||||
break
|
||||
case actions.SET_POLICIES:
|
||||
newState.policies = action.policies
|
||||
break
|
||||
case actions.DELETE_CONFIRMATION:
|
||||
newState.deleteConfirmation = Object.assign({}, action.payload)
|
||||
break
|
||||
case actions.SET_SHARE_OBJECT:
|
||||
newState.shareObject = Object.assign({}, action.shareObject)
|
||||
break
|
||||
case actions.SET_PREFIX_WRITABLE:
|
||||
newState.prefixWritable = action.prefixWritable
|
||||
break
|
||||
case actions.REMOVE_OBJECT:
|
||||
let idx = newState.objects.findIndex(object => object.name === action.object)
|
||||
if (idx == -1) break
|
||||
newState.objects = [...newState.objects.slice(0, idx), ...newState.objects.slice(idx + 1)]
|
||||
break
|
||||
}
|
||||
return newState
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { minioBrowserPrefix } from './constants.js'
|
||||
|
||||
export const sortObjectsByName = (objects, order) => {
|
||||
let folders = objects.filter(object => object.name.endsWith('/'))
|
||||
let files = objects.filter(object => !object.name.endsWith('/'))
|
||||
folders = folders.sort((a, b) => {
|
||||
if (a.name.toLowerCase() < b.name.toLowerCase()) return -1
|
||||
if (a.name.toLowerCase() > b.name.toLowerCase()) return 1
|
||||
return 0
|
||||
})
|
||||
files = files.sort((a, b) => {
|
||||
if (a.name.toLowerCase() < b.name.toLowerCase()) return -1
|
||||
if (a.name.toLowerCase() > b.name.toLowerCase()) return 1
|
||||
return 0
|
||||
})
|
||||
if (order) {
|
||||
folders = folders.reverse()
|
||||
files = files.reverse()
|
||||
}
|
||||
return [...folders, ...files]
|
||||
}
|
||||
|
||||
export const sortObjectsBySize = (objects, order) => {
|
||||
let folders = objects.filter(object => object.name.endsWith('/'))
|
||||
let files = objects.filter(object => !object.name.endsWith('/'))
|
||||
files = files.sort((a, b) => a.size - b.size)
|
||||
if (order)
|
||||
files = files.reverse()
|
||||
return [...folders, ...files]
|
||||
}
|
||||
|
||||
export const sortObjectsByDate = (objects, order) => {
|
||||
let folders = objects.filter(object => object.name.endsWith('/'))
|
||||
let files = objects.filter(object => !object.name.endsWith('/'))
|
||||
files = files.sort((a, b) => new Date(a.lastModified).getTime() - new Date(b.lastModified).getTime())
|
||||
if (order)
|
||||
files = files.reverse()
|
||||
return [...folders, ...files]
|
||||
}
|
||||
|
||||
export const pathSlice = (path) => {
|
||||
path = path.replace(minioBrowserPrefix, '')
|
||||
let prefix = ''
|
||||
let bucket = ''
|
||||
if (!path) return {
|
||||
bucket,
|
||||
prefix
|
||||
}
|
||||
let objectIndex = path.indexOf('/', 1)
|
||||
if (objectIndex == -1) {
|
||||
bucket = path.slice(1)
|
||||
return {
|
||||
bucket,
|
||||
prefix
|
||||
}
|
||||
}
|
||||
bucket = path.slice(1, objectIndex)
|
||||
prefix = path.slice(objectIndex + 1)
|
||||
return {
|
||||
bucket,
|
||||
prefix
|
||||
}
|
||||
}
|
||||
|
||||
export const pathJoin = (bucket, prefix) => {
|
||||
if (!prefix)
|
||||
prefix = ''
|
||||
return minioBrowserPrefix + '/' + bucket + '/' + prefix
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { browserHistory } from 'react-router'
|
||||
import JSONrpc from './jsonrpc'
|
||||
import * as actions from './actions'
|
||||
import { minioBrowserPrefix } from './constants.js'
|
||||
import Moment from 'moment'
|
||||
import storage from 'local-storage-fallback'
|
||||
|
||||
export default class Web {
|
||||
constructor(endpoint, dispatch) {
|
||||
const namespace = 'Web'
|
||||
this.dispatch = dispatch
|
||||
this.JSONrpc = new JSONrpc({
|
||||
endpoint,
|
||||
namespace
|
||||
})
|
||||
}
|
||||
makeCall(method, options) {
|
||||
return this.JSONrpc.call(method, {
|
||||
params: options
|
||||
}, storage.getItem('token'))
|
||||
.catch(err => {
|
||||
if (err.status === 401) {
|
||||
storage.removeItem('token')
|
||||
browserHistory.push(`${minioBrowserPrefix}/login`)
|
||||
throw new Error('Please re-login.')
|
||||
}
|
||||
if (err.status)
|
||||
throw new Error(`Server returned error [${err.status}]`)
|
||||
throw new Error('Minio server is unreachable')
|
||||
})
|
||||
.then(res => {
|
||||
let json = JSON.parse(res.text)
|
||||
let result = json.result
|
||||
let error = json.error
|
||||
if (error) {
|
||||
throw new Error(error.message)
|
||||
}
|
||||
if (!Moment(result.uiVersion).isValid()) {
|
||||
throw new Error("Invalid UI version in the JSON-RPC response")
|
||||
}
|
||||
if (result.uiVersion !== currentUiVersion
|
||||
&& currentUiVersion !== 'MINIO_UI_VERSION') {
|
||||
storage.setItem('newlyUpdated', true)
|
||||
location.reload()
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
LoggedIn() {
|
||||
return !!storage.getItem('token')
|
||||
}
|
||||
Login(args) {
|
||||
return this.makeCall('Login', args)
|
||||
.then(res => {
|
||||
storage.setItem('token', `${res.token}`)
|
||||
return res
|
||||
})
|
||||
}
|
||||
Logout() {
|
||||
storage.removeItem('token')
|
||||
}
|
||||
ServerInfo() {
|
||||
return this.makeCall('ServerInfo')
|
||||
}
|
||||
StorageInfo() {
|
||||
return this.makeCall('StorageInfo')
|
||||
}
|
||||
ListBuckets() {
|
||||
return this.makeCall('ListBuckets')
|
||||
}
|
||||
MakeBucket(args) {
|
||||
return this.makeCall('MakeBucket', args)
|
||||
}
|
||||
ListObjects(args) {
|
||||
return this.makeCall('ListObjects', args)
|
||||
}
|
||||
PresignedGet(args) {
|
||||
return this.makeCall('PresignedGet', args)
|
||||
}
|
||||
PutObjectURL(args) {
|
||||
return this.makeCall('PutObjectURL', args)
|
||||
}
|
||||
RemoveObject(args) {
|
||||
return this.makeCall('RemoveObject', args)
|
||||
}
|
||||
GetAuth() {
|
||||
return this.makeCall('GetAuth')
|
||||
}
|
||||
GenerateAuth() {
|
||||
return this.makeCall('GenerateAuth')
|
||||
}
|
||||
SetAuth(args) {
|
||||
return this.makeCall('SetAuth', args)
|
||||
.then(res => {
|
||||
storage.setItem('token', `${res.token}`)
|
||||
return res
|
||||
})
|
||||
}
|
||||
GetBucketPolicy(args) {
|
||||
return this.makeCall('GetBucketPolicy', args)
|
||||
}
|
||||
SetBucketPolicy(args) {
|
||||
return this.makeCall('SetBucketPolicy', args)
|
||||
}
|
||||
ListAllBucketPolicies(args) {
|
||||
return this.makeCall('ListAllBucketPolicies', args)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
.alert {
|
||||
border: 0;
|
||||
position: fixed;
|
||||
max-width: 500px;
|
||||
margin: 0;
|
||||
box-shadow: 0 4px 5px rgba(0, 0, 0, 0.1);
|
||||
color: @white;
|
||||
width: 100%;
|
||||
right: 20px;
|
||||
border-radius: 3px;
|
||||
padding: 17px 50px 17px 17px;
|
||||
z-index: 10010;
|
||||
.animation-duration(800ms);
|
||||
.animation-fill-mode(both);
|
||||
|
||||
&:not(.progress) {
|
||||
top: 20px;
|
||||
|
||||
@media(min-width: (@screen-sm-min)) {
|
||||
left: 50%;
|
||||
margin-left: -250px;
|
||||
}
|
||||
}
|
||||
|
||||
&.progress {
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
&.alert-danger {
|
||||
background: @red;
|
||||
}
|
||||
|
||||
&.alert-success {
|
||||
background: @green;
|
||||
}
|
||||
|
||||
&.alert-info {
|
||||
background: @blue;
|
||||
}
|
||||
|
||||
@media(max-width: (@screen-xs-max)) {
|
||||
left: 20px;
|
||||
width: ~"calc(100% - 40px)";
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.progress {
|
||||
margin: 10px 10px 8px 0;
|
||||
height: 5px;
|
||||
box-shadow: none;
|
||||
border-radius: 1px;
|
||||
background-color: @blue;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
box-shadow: none;
|
||||
background-color: @white;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.close {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
.animated{
|
||||
&.infinite {
|
||||
.animation-iteration-count(infinite);
|
||||
}
|
||||
}
|
||||
|
||||
@import 'fadeIn';
|
||||
@import 'fadeInDown';
|
||||
@import 'fadeInUp';
|
||||
@import 'fadeOut';
|
||||
@import 'fadeOutDown';
|
||||
@import 'fadeOutUp';
|
||||
@import 'zoomIn';
|
||||
@@ -0,0 +1,26 @@
|
||||
@-webkit-keyframes fadeIn {
|
||||
0% {opacity: 0;}
|
||||
100% {opacity: 1;}
|
||||
}
|
||||
|
||||
@-moz-keyframes fadeIn {
|
||||
0% {opacity: 0;}
|
||||
100% {opacity: 1;}
|
||||
}
|
||||
|
||||
@-o-keyframes fadeIn {
|
||||
0% {opacity: 0;}
|
||||
100% {opacity: 1;}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
0% {opacity: 0;}
|
||||
100% {opacity: 1;}
|
||||
}
|
||||
|
||||
.fadeIn {
|
||||
-webkit-animation-name: fadeIn;
|
||||
-moz-animation-name: fadeIn;
|
||||
-o-animation-name: fadeIn;
|
||||
animation-name: fadeIn;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
@-webkit-keyframes fadeInDown {
|
||||
0% {
|
||||
opacity: 0;
|
||||
-webkit-transform: translateY(-20px);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
-webkit-transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@-moz-keyframes fadeInDown {
|
||||
0% {
|
||||
opacity: 0;
|
||||
-moz-transform: translateY(-20px);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
-moz-transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@-o-keyframes fadeInDown {
|
||||
0% {
|
||||
opacity: 0;
|
||||
-ms-transform: translateY(-20px);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
-ms-transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInDown {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.fadeInDown {
|
||||
-webkit-animation-name: fadeInDown;
|
||||
-moz-animation-name: fadeInDown;
|
||||
-o-animation-name: fadeInDown;
|
||||
animation-name: fadeInDown;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
@-webkit-keyframes fadeInUp {
|
||||
0% {
|
||||
opacity: 0;
|
||||
-webkit-transform: translateY(20px);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
-webkit-transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@-moz-keyframes fadeInUp {
|
||||
0% {
|
||||
opacity: 0;
|
||||
-moz-transform: translateY(20px);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
-moz-transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@-o-keyframes fadeInUp {
|
||||
0% {
|
||||
opacity: 0;
|
||||
-o-transform: translateY(20px);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
-o-transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.fadeInUp {
|
||||
-webkit-animation-name: fadeInUp;
|
||||
-moz-animation-name: fadeInUp;
|
||||
-o-animation-name: fadeInUp;
|
||||
animation-name: fadeInUp;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
@-webkit-keyframes fadeOut {
|
||||
0% {opacity: 1;}
|
||||
100% {opacity: 0;}
|
||||
}
|
||||
|
||||
@-moz-keyframes fadeOut {
|
||||
0% {opacity: 1;}
|
||||
100% {opacity: 0;}
|
||||
}
|
||||
|
||||
@-o-keyframes fadeOut {
|
||||
0% {opacity: 1;}
|
||||
100% {opacity: 0;}
|
||||
}
|
||||
|
||||
@keyframes fadeOut {
|
||||
0% {opacity: 1;}
|
||||
100% {opacity: 0;}
|
||||
}
|
||||
|
||||
.fadeOut {
|
||||
-webkit-animation-name: fadeOut;
|
||||
-moz-animation-name: fadeOut;
|
||||
-o-animation-name: fadeOut;
|
||||
animation-name: fadeOut;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
@-webkit-keyframes fadeOutDown {
|
||||
0% {
|
||||
opacity: 1;
|
||||
-webkit-transform: translateY(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
-webkit-transform: translateY(20px);
|
||||
}
|
||||
}
|
||||
|
||||
@-moz-keyframes fadeOutDown {
|
||||
0% {
|
||||
opacity: 1;
|
||||
-moz-transform: translateY(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
-moz-transform: translateY(20px);
|
||||
}
|
||||
}
|
||||
|
||||
@-o-keyframes fadeOutDown {
|
||||
0% {
|
||||
opacity: 1;
|
||||
-o-transform: translateY(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
-o-transform: translateY(20px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeOutDown {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
}
|
||||
|
||||
.fadeOutDown {
|
||||
-webkit-animation-name: fadeOutDown;
|
||||
-moz-animation-name: fadeOutDown;
|
||||
-o-animation-name: fadeOutDown;
|
||||
animation-name: fadeOutDown;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
@-webkit-keyframes fadeOutUp {
|
||||
0% {
|
||||
opacity: 1;
|
||||
-webkit-transform: translateY(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
-webkit-transform: translateY(-20px);
|
||||
}
|
||||
}
|
||||
@-moz-keyframes fadeOutUp {
|
||||
0% {
|
||||
opacity: 1;
|
||||
-moz-transform: translateY(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
-moz-transform: translateY(-20px);
|
||||
}
|
||||
}
|
||||
@-o-keyframes fadeOutUp {
|
||||
0% {
|
||||
opacity: 1;
|
||||
-o-transform: translateY(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
-o-transform: translateY(-20px);
|
||||
}
|
||||
}
|
||||
@keyframes fadeOutUp {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
}
|
||||
|
||||
.fadeOutUp {
|
||||
-webkit-animation-name: fadeOutUp;
|
||||
-moz-animation-name: fadeOutUp;
|
||||
-o-animation-name: fadeOutUp;
|
||||
animation-name: fadeOutUp;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
@-webkit-keyframes zoomIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
-webkit-transform: scale3d(.3, .3, .3);
|
||||
transform: scale3d(.3, .3, .3);
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes zoomIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
-webkit-transform: scale3d(.3, .3, .3);
|
||||
transform: scale3d(.3, .3, .3);
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
* {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
&:focus,
|
||||
&:active {
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 10px;
|
||||
-webkit-tap-highlight-color: rgba(0,0,0,0);
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
a {
|
||||
.transition(color);
|
||||
.transition-duration(300ms);
|
||||
|
||||
}
|
||||
|
||||
button {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
.btn {
|
||||
border: 0;
|
||||
padding: 5px 10px;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
border-radius: 2px;
|
||||
text-align: center;
|
||||
.transition(all);
|
||||
.transition-duration(300ms);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
.opacity(0.9);
|
||||
}
|
||||
}
|
||||
|
||||
/*-----------------------------------
|
||||
Button Variants
|
||||
------------------------------------*/
|
||||
.btn-variant(@bg-color, @color) {
|
||||
color: @color;
|
||||
background-color: @bg-color;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: @color;
|
||||
background-color: darken(@bg-color, 6%);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
.btn-block {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn-link {
|
||||
.btn-variant(#eee, #545454);
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
.btn-variant(@red, @white);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
.btn-variant(@blue, @white);
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
.btn-variant(@green, @white);
|
||||
}
|
||||
//-----------------------------------
|
||||
@@ -0,0 +1,26 @@
|
||||
.dropdown-menu {
|
||||
padding: 15px 0;
|
||||
top: 0;
|
||||
margin-top: -1px;
|
||||
|
||||
& > li {
|
||||
& > a {
|
||||
padding: 8px 20px;
|
||||
font-size: 15px;
|
||||
|
||||
& > i {
|
||||
width: 20px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu-right {
|
||||
& > li {
|
||||
& > a {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/*------------------------------
|
||||
Layout
|
||||
--------------------------------*/
|
||||
.file-explorer {
|
||||
background-color: @white;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
|
||||
&.toggled {
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.fe-body {
|
||||
@media(min-width: @screen-md-min) {
|
||||
padding: 0 0 40px @fe-sidebar-width;
|
||||
}
|
||||
|
||||
@media(max-width: @screen-sm-max) {
|
||||
padding: 75px 0 80px;
|
||||
}
|
||||
|
||||
min-height:100vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
|
||||
/*------------------------------
|
||||
Create and Upload Button
|
||||
--------------------------------*/
|
||||
.feb-actions {
|
||||
position: fixed;
|
||||
bottom: 30px;
|
||||
right: 30px;
|
||||
|
||||
.dropdown-menu {
|
||||
min-width: 55px;
|
||||
width: 55px;
|
||||
text-align: center;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&.open {
|
||||
.feba-btn {
|
||||
.scale(1);
|
||||
|
||||
&:first-child {
|
||||
.animation-name(feba-btn-anim);
|
||||
.animation-duration(300ms);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
.animation-name(feba-btn-anim);
|
||||
.animation-duration(100ms);
|
||||
}
|
||||
}
|
||||
|
||||
.feba-toggle {
|
||||
background: darken(@red, 10%);
|
||||
|
||||
& > span {
|
||||
.rotate(135deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.feba-toggle {
|
||||
width: 55px;
|
||||
height: 55px;
|
||||
line-height: 55px;
|
||||
border-radius: 50%;
|
||||
background: @red;
|
||||
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.15);
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
i {
|
||||
color: @white;
|
||||
font-size: 17px;
|
||||
line-height: 58px;
|
||||
}
|
||||
}
|
||||
|
||||
.feba-toggle,
|
||||
.feba-toggle > span {
|
||||
.transition(all);
|
||||
.transition-duration(250ms);
|
||||
.backface-visibility(hidden);
|
||||
}
|
||||
|
||||
.feba-btn {
|
||||
width: 40px;
|
||||
margin-top: 10px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
color: @white;
|
||||
line-height: 40px;
|
||||
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.15);
|
||||
-webkit-transform: scale(0);
|
||||
transform: scale(0);
|
||||
position: relative;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: @white;
|
||||
}
|
||||
|
||||
label {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.feba-bucket {
|
||||
background: @orange;
|
||||
}
|
||||
|
||||
.feba-upload {
|
||||
background: @yellow;
|
||||
}
|
||||
|
||||
@-webkit-keyframes feba-btn-anim {
|
||||
from {
|
||||
.scale(0);
|
||||
.opacity(0);
|
||||
}
|
||||
to {
|
||||
.scale(1);
|
||||
.opacity(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes feba-btn-anim {
|
||||
from {
|
||||
.scale(0);
|
||||
.opacity(0);
|
||||
}
|
||||
to {
|
||||
.scale(1);
|
||||
.opacity(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url('../../fonts/lato/lato-normal.woff2') format('woff2'),
|
||||
url('../../fonts/lato/lato-normal.woff') format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
.form-control {
|
||||
border: 0;
|
||||
border-bottom: 1px solid @input-border;
|
||||
color: #32393F;
|
||||
padding: 5px;
|
||||
width: 100%;
|
||||
font-size: 13px;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
select.form-control {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
border-radius: 0;
|
||||
background: url(../../img/select-caret.svg) no-repeat bottom 7px right;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*--------------------------
|
||||
Input Group
|
||||
----------------------------*/
|
||||
.input-group {
|
||||
position: relative;
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
label:not(.ig-label) {
|
||||
font-size: 13px;
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.ig-label {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
bottom: 7px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
.transition(all);
|
||||
.transition-duration(250ms);
|
||||
padding: 2px 0 3px;
|
||||
border-radius: 2px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.ig-helpers {
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
|
||||
&,
|
||||
&:before,
|
||||
&:after {
|
||||
position: absolute;
|
||||
height: 2px;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
content: '';
|
||||
width: 0;
|
||||
.transition(all);
|
||||
.transition-duration(250ms);
|
||||
background-color: #03A9F4;
|
||||
}
|
||||
|
||||
&:before {
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
&:after {
|
||||
right: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.ig-text {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
border-bottom: 1px solid #eee;
|
||||
color: #32393F;
|
||||
font-size: 13px;
|
||||
|
||||
|
||||
&:focus + .ig-helpers {
|
||||
&:before,
|
||||
&:after {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
&:valid,
|
||||
&:disabled,
|
||||
&:focus {
|
||||
& ~ .ig-label {
|
||||
bottom: 35px;
|
||||
font-size: 13px;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
.opacity(0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.ig-dark {
|
||||
.ig-text {
|
||||
color: @white;
|
||||
border-color: rgba(255,255,255,0.1);
|
||||
}
|
||||
|
||||
.ig-helpers {
|
||||
&:before,
|
||||
&:after {
|
||||
background-color: #dfdfdf;
|
||||
height: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ig-left {
|
||||
.ig-label,
|
||||
.ig-text {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.ig-error {
|
||||
.ig-label {
|
||||
color: #E23F3F;
|
||||
}
|
||||
.ig-helpers i {
|
||||
&:first-child,
|
||||
&:first-child:before,
|
||||
&:first-child:after {
|
||||
background: rgba(226, 63, 63, 0.43);
|
||||
}
|
||||
&:last-child,
|
||||
&:last-child:before,
|
||||
&:last-child:after {
|
||||
background: #E23F3F !important;
|
||||
}
|
||||
}
|
||||
&:after {
|
||||
content: "\f05a";
|
||||
font-family: FontAwesome;
|
||||
position: absolute;
|
||||
top: 17px;
|
||||
right: 9px;
|
||||
font-size: 20px;
|
||||
color: #D33D3E;
|
||||
}
|
||||
}
|
||||
|
||||
.ig-search {
|
||||
&:before {
|
||||
font-family: @font-family-icon;
|
||||
content: '\f002';
|
||||
font-size: 15px;
|
||||
position: absolute;
|
||||
left: 2px;
|
||||
top: 8px;
|
||||
}
|
||||
|
||||
.ig-text {
|
||||
padding-left: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*--------------------------
|
||||
Share Spinners
|
||||
----------------------------*/
|
||||
.set-expire {
|
||||
border: 1px solid @input-border;
|
||||
margin: 35px 0 30px;
|
||||
}
|
||||
|
||||
.set-expire-item {
|
||||
padding: 9px 5px 3px;
|
||||
position: relative;
|
||||
display: table-cell;
|
||||
width: 1%;
|
||||
text-align: center;
|
||||
|
||||
&:not(:last-child) {
|
||||
border-right: 1px solid @input-border;
|
||||
}
|
||||
}
|
||||
|
||||
.set-expire-title {
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.set-expire-value {
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
left: -8px;
|
||||
|
||||
input {
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
right: -15px;
|
||||
border: 0;
|
||||
color: @text-strong-color;
|
||||
padding: 0;
|
||||
height: 25px;
|
||||
width: 100%;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.set-expire-decrease,
|
||||
.set-expire-increase {
|
||||
position: absolute;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: url(../../img/arrow.svg) no-repeat center;
|
||||
background-size: 85%;
|
||||
left: 50%;
|
||||
margin-left: -10px;
|
||||
.opacity(0.2);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
.opacity(0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.set-expire-increase {
|
||||
top: -25px;
|
||||
}
|
||||
|
||||
.set-expire-decrease {
|
||||
bottom: -27px;
|
||||
.rotate(-180deg);
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*----------------------------
|
||||
Text Alignment
|
||||
-----------------------------*/
|
||||
.text-center { text-align: center !important; }
|
||||
.text-left { text-align: left !important; }
|
||||
.text-right { text-align: right !important; }
|
||||
|
||||
|
||||
/*----------------------------
|
||||
Float
|
||||
-----------------------------*/
|
||||
.clearfix { .clearfix(); }
|
||||
.pull-right { float: right !important; }
|
||||
.pull-left { float: left !important; }
|
||||
|
||||
|
||||
/*----------------------------
|
||||
Position
|
||||
-----------------------------*/
|
||||
.p-relative { position: relative; }
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
Generate Margin Class
|
||||
margin, margin-top, margin-bottom, margin-left, margin-right
|
||||
----------------------------------------------------------------------------*/
|
||||
|
||||
.margin (@label, @size: 1, @key:1) when (@size =< 30){
|
||||
.m-@{key} {
|
||||
margin: @size !important;
|
||||
}
|
||||
|
||||
.m-t-@{key} {
|
||||
margin-top: @size !important;
|
||||
}
|
||||
|
||||
.m-b-@{key} {
|
||||
margin-bottom: @size !important;
|
||||
}
|
||||
|
||||
.m-l-@{key} {
|
||||
margin-left: @size !important;
|
||||
}
|
||||
|
||||
.m-r-@{key} {
|
||||
margin-right: @size !important;
|
||||
}
|
||||
|
||||
.margin(@label - 5; @size + 5; @key + 5);
|
||||
}
|
||||
|
||||
.margin(25, 0px, 0);
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
Generate Padding Class
|
||||
padding, padding-top, padding-bottom, padding-left, padding-right
|
||||
----------------------------------------------------------------------------*/
|
||||
.padding (@label, @size: 1, @key:1) when (@size =< 30){
|
||||
.p-@{key} {
|
||||
padding: @size !important;
|
||||
}
|
||||
|
||||
.p-t-@{key} {
|
||||
padding-top: @size !important;
|
||||
}
|
||||
|
||||
.p-b-@{key} {
|
||||
padding-bottom: @size !important;
|
||||
}
|
||||
|
||||
.p-l-@{key} {
|
||||
padding-left: @size !important;
|
||||
}
|
||||
|
||||
.p-r-@{key} {
|
||||
padding-right: @size !important;
|
||||
}
|
||||
|
||||
.padding(@label - 5; @size + 5; @key + 5);
|
||||
}
|
||||
|
||||
.padding(25, 0px, 0);
|
||||
@@ -0,0 +1,242 @@
|
||||
/*--------------------------
|
||||
Header
|
||||
----------------------------*/
|
||||
.fe-header {
|
||||
padding: 45px 55px 20px;
|
||||
|
||||
@media(min-width: @screen-md-min) {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@media(max-width: (@screen-xs-max - 100)) {
|
||||
padding: 25px 25px 20px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 16px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
|
||||
& > span {
|
||||
margin-bottom: 7px;
|
||||
display: inline-block;
|
||||
|
||||
&:not(:first-child) {
|
||||
&:before {
|
||||
content: '/';
|
||||
margin: 0 4px;
|
||||
color: @text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*--------------------------
|
||||
Disk usage
|
||||
----------------------------*/
|
||||
.feh-usage {
|
||||
margin-top: 12px;
|
||||
max-width: 285px;
|
||||
|
||||
@media(max-width: (@screen-xs-max - 100px)) {
|
||||
max-width: 100%;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
& > ul {
|
||||
margin-top: 7px;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
|
||||
& > li {
|
||||
padding-right: 0;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fehu-chart {
|
||||
height: 5px;
|
||||
background: #eee;
|
||||
position: relative;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
|
||||
& > div {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
background: @link-color;
|
||||
}
|
||||
}
|
||||
|
||||
/*--------------------------
|
||||
Header Actions
|
||||
----------------------------*/
|
||||
.feh-actions {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
right: 35px;
|
||||
top: 30px;
|
||||
z-index: 11;
|
||||
|
||||
@media(max-width: (@screen-sm-max)) {
|
||||
top: 7px;
|
||||
right: 10px;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
& > li {
|
||||
display: inline-block;
|
||||
text-align: right;
|
||||
vertical-align: top;
|
||||
line-height: 100%;
|
||||
|
||||
& > a,
|
||||
& > .btn-group > button {
|
||||
display: block;
|
||||
height: 45px;
|
||||
min-width: 45px;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: none;
|
||||
|
||||
@media(min-width: @screen-md-min) {
|
||||
color: #7B7B7B;
|
||||
font-size: 21px;
|
||||
line-height: 45px;
|
||||
.transition(all);
|
||||
.transition-duration(300ms);
|
||||
|
||||
&:hover {
|
||||
background: rgba(0,0,0,0.09);
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: (@screen-sm-max)) {
|
||||
background: url(../../img/more-h-light.svg) no-repeat center;
|
||||
|
||||
.fa-reorder {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*--------------------------
|
||||
Mobile Header
|
||||
----------------------------*/
|
||||
@media(max-width: @screen-sm-max) {
|
||||
.fe-header-mobile {
|
||||
background-color: @dark-gray;
|
||||
padding: 10px 50px 9px 12px;
|
||||
text-align: center;
|
||||
position: fixed;
|
||||
z-index: 10;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.3);
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
|
||||
.mh-logo {
|
||||
height: 35px;
|
||||
position: relative;
|
||||
top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.feh-trigger {
|
||||
width: 41px;
|
||||
height: 41px;
|
||||
cursor: pointer;
|
||||
float: left;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
|
||||
}
|
||||
|
||||
&:after {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&:before {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
.transition(all);
|
||||
.transition-duration(300ms);
|
||||
.scale(0);
|
||||
}
|
||||
}
|
||||
|
||||
.feht-toggled {
|
||||
&:before {
|
||||
.scale(1);
|
||||
}
|
||||
|
||||
.feht-lines {
|
||||
.rotate(180deg);
|
||||
|
||||
& > div {
|
||||
&.top {
|
||||
width: 12px;
|
||||
transform: translateX(8px) translateY(1px) rotate(45deg);
|
||||
-webkit-transform: translateX(8px) translateY(1px) rotate(45deg);
|
||||
}
|
||||
|
||||
&.bottom {
|
||||
width: 12px;
|
||||
transform: translateX(8px) translateY(-1px) rotate(-45deg);
|
||||
-webkit-transform: translateX(8px) translateY(-1px) rotate(-45deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.feht-lines,
|
||||
.feht-lines > div {
|
||||
.transition(all);
|
||||
.transition-duration(300ms);
|
||||
}
|
||||
|
||||
.feht-lines {
|
||||
width: 18px;
|
||||
height: 12px;
|
||||
display: inline-block;
|
||||
margin-top: 14px;
|
||||
|
||||
& > div {
|
||||
background-color: #EAEAEA;
|
||||
width: 18px;
|
||||
height: 2px;
|
||||
|
||||
&.center {
|
||||
margin: 3px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
.ie-warning {
|
||||
background-color: #ff5252;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
text-align: center;
|
||||
|
||||
&:before {
|
||||
width: 1px;
|
||||
content: '';
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&:before,
|
||||
.iw-inner {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.iw-inner {
|
||||
width: 470px;
|
||||
height: 300px;
|
||||
background-color: @white;
|
||||
border-radius: 5px;
|
||||
padding: 40px;
|
||||
position: relative;
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 230px;
|
||||
margin-left: 80px;
|
||||
margin-top: 16px;
|
||||
|
||||
& > li {
|
||||
float: left;
|
||||
|
||||
& > a {
|
||||
display: block;
|
||||
padding: 10px 15px 7px;
|
||||
font-size: 14px;
|
||||
margin: 0 1px;
|
||||
border-radius: 3px;
|
||||
|
||||
&:hover {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 40px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.iwi-icon {
|
||||
color: #ff5252;
|
||||
font-size: 40px;
|
||||
display: block;
|
||||
line-height: 100%;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.iwi-skip {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: -35px;
|
||||
width: 100%;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: @white;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,352 @@
|
||||
/*--------------------------
|
||||
Row
|
||||
----------------------------*/
|
||||
.fesl-row {
|
||||
padding-right: 40px;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
position: relative;
|
||||
|
||||
@media (min-width: (@screen-sm-min - 100px)) {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.clearfix();
|
||||
}
|
||||
|
||||
header.fesl-row {
|
||||
@media (min-width:(@screen-sm-min - 100px)) {
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 1px solid lighten(@text-muted-color, 20%);
|
||||
padding-left: 40px;
|
||||
|
||||
.fesl-item,
|
||||
.fesli-sort {
|
||||
.transition(all);
|
||||
.transition-duration(300ms);
|
||||
}
|
||||
|
||||
.fesl-item {
|
||||
cursor: pointer;
|
||||
color: @text-color;
|
||||
font-weight: 500;
|
||||
margin-bottom: -5px;
|
||||
|
||||
& > .fesli-sort {
|
||||
float: right;
|
||||
margin: 4px 0 0;
|
||||
.opacity(0);
|
||||
color: @dark-gray;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&:hover:not(.fi-actions) {
|
||||
background: lighten(@text-muted-color, 22%);
|
||||
color: @dark-gray;
|
||||
|
||||
& > .fesli-sort {
|
||||
.opacity(0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width:(@screen-xs-max - 100px)) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
div.fesl-row {
|
||||
padding-left: 85px;
|
||||
border-bottom: 1px solid transparent;
|
||||
cursor: default;
|
||||
|
||||
@media (max-width: (@screen-xs-max - 100px)) {
|
||||
padding-left: 70px;
|
||||
padding-right: 45px;
|
||||
}
|
||||
|
||||
&:nth-child(even) {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #fbf7dc;
|
||||
}
|
||||
|
||||
&[data-type]:before {
|
||||
font-family: @font-family-icon;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
text-align: center;
|
||||
line-height: 35px;
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
font-size: 16px;
|
||||
left: 50px;
|
||||
top: 9px;
|
||||
color: @white;
|
||||
|
||||
@media (max-width: (@screen-xs-max - 100px)) {
|
||||
left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
&[data-type="folder"] {
|
||||
@media (max-width: (@screen-xs-max - 100px)) {
|
||||
.fesl-item {
|
||||
&.fi-name {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 7px;
|
||||
}
|
||||
|
||||
&.fi-size,
|
||||
&.fi-modified {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*--------------------------
|
||||
Icons
|
||||
----------------------------*/
|
||||
&[data-type=folder]:before {
|
||||
content: '\f114';
|
||||
background-color: #a1d6dd;
|
||||
}
|
||||
&[data-type=pdf]:before {
|
||||
content: "\f1c1";
|
||||
background-color: #fa7775;
|
||||
}
|
||||
&[data-type=zip]:before {
|
||||
content: "\f1c6";
|
||||
background-color: #427089;
|
||||
}
|
||||
&[data-type=audio]:before {
|
||||
content: "\f1c7";
|
||||
background-color: #009688
|
||||
}
|
||||
&[data-type=code]:before {
|
||||
content: "\f1c9";
|
||||
background-color: #997867;
|
||||
}
|
||||
&[data-type=excel]:before {
|
||||
content: "\f1c3";
|
||||
background-color: #64c866;
|
||||
}
|
||||
&[data-type=image]:before {
|
||||
content: "\f1c5";
|
||||
background-color: #f06292;
|
||||
}
|
||||
&[data-type=video]:before {
|
||||
content: "\f1c8";
|
||||
background-color: #f8c363;
|
||||
}
|
||||
&[data-type=other]:before {
|
||||
content: "\f016";
|
||||
background-color: #afafaf;
|
||||
}
|
||||
&[data-type=text]:before {
|
||||
content: "\f0f6";
|
||||
background-color: #8a8a8a;
|
||||
}
|
||||
&[data-type=doc]:before {
|
||||
content: "\f1c2";
|
||||
background-color: #2196f5;
|
||||
}
|
||||
&[data-type=presentation]:before {
|
||||
content: "\f1c4";
|
||||
background-color: #896ea6;
|
||||
}
|
||||
|
||||
&.fesl-loading{
|
||||
&:before {
|
||||
content: '';
|
||||
}
|
||||
|
||||
&:after {
|
||||
.list-loader(20px, 20px, rgba(255, 255, 255, 0.5), @white);
|
||||
left: 57px;
|
||||
top: 17px;
|
||||
|
||||
@media (max-width: (@screen-xs-max - 100px)) {
|
||||
left: 27px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*--------------------------
|
||||
Files and Folders
|
||||
----------------------------*/
|
||||
.fesl-item {
|
||||
display: block;
|
||||
|
||||
a {
|
||||
color: darken(@text-color, 5%);
|
||||
}
|
||||
|
||||
@media(min-width: (@screen-sm-min - 100px)) {
|
||||
&:not(.fi-actions) {
|
||||
text-overflow: ellipsis;
|
||||
padding: 10px 15px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&.fi-name {
|
||||
flex: 3;
|
||||
}
|
||||
|
||||
&.fi-size {
|
||||
width: 140px;
|
||||
}
|
||||
|
||||
&.fi-modified {
|
||||
width: 190px;
|
||||
}
|
||||
|
||||
&.fi-actions {
|
||||
width: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: (@screen-xs-max - 100px)) {
|
||||
padding: 0;
|
||||
|
||||
&.fi-name {
|
||||
width: 100%;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
&.fi-size,
|
||||
&.fi-modified {
|
||||
font-size: 12px;
|
||||
color: #B5B5B5;
|
||||
float: left;
|
||||
}
|
||||
|
||||
&.fi-modified {
|
||||
max-width: 72px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&.fi-size {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
&.fi-actions {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*--------------------------
|
||||
Action buttons
|
||||
----------------------------*/
|
||||
.fia-toggle {
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
background: transparent url(../../img/more-h.svg) no-repeat center;
|
||||
position: relative;
|
||||
top: 3px;
|
||||
.opacity(0.4);
|
||||
|
||||
&:hover {
|
||||
.opacity(0.7);
|
||||
}
|
||||
}
|
||||
|
||||
.fi-actions {
|
||||
.dropdown-menu {
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
right: 38px;
|
||||
left: auto;
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
&.open {
|
||||
.dropdown-menu {
|
||||
.fiad-action {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fiad-action {
|
||||
height: 35px;
|
||||
width: 35px;
|
||||
background: @amber;
|
||||
display: inline-block;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
line-height: 35px;
|
||||
font-weight: normal;
|
||||
position: relative;
|
||||
top: 4px;
|
||||
margin-left: 5px;
|
||||
.animation-name(fiad-action-anim);
|
||||
.transform-origin(center center);
|
||||
.backface-visibility(none);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
|
||||
&:nth-child(2) {
|
||||
.animation-duration(100ms);
|
||||
}
|
||||
|
||||
&:nth-child(1) {
|
||||
.animation-duration(250ms);
|
||||
}
|
||||
|
||||
& > i {
|
||||
font-size: 14px;
|
||||
color: @white;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: darken(@amber, 3%);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@-webkit-keyframes fiad-action-anim {
|
||||
from {
|
||||
.scale(0);
|
||||
.opacity(0);
|
||||
right: -20px;
|
||||
}
|
||||
to {
|
||||
.scale(1);
|
||||
.opacity(1);
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fiad-action-anim {
|
||||
from {
|
||||
.scale(0);
|
||||
.opacity(0);
|
||||
right: -20px;
|
||||
}
|
||||
to {
|
||||
.scale(1);
|
||||
.opacity(1);
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
.login {
|
||||
height: 100vh;
|
||||
min-height: 500px;
|
||||
background: @dark-gray;
|
||||
|
||||
text-align: center;
|
||||
&:before {
|
||||
height: ~"calc(100% - 110px)";
|
||||
width: 1px;
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
|
||||
.l-wrap,
|
||||
.login:before {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.l-wrap {
|
||||
width: 80%;
|
||||
max-width: 500px;
|
||||
margin-top: -50px;
|
||||
&.toggled {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.input-group:not(:last-child) {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.l-footer {
|
||||
height: 110px;
|
||||
padding: 0 50px;
|
||||
}
|
||||
|
||||
.lf-logo {
|
||||
float: right;
|
||||
img {
|
||||
width: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.lf-server {
|
||||
float: left;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
padding-top: 40px;
|
||||
}
|
||||
|
||||
@media (max-width: @screen-sm-min) {
|
||||
.lf-logo,
|
||||
.lf-server {
|
||||
float: none;
|
||||
display: block;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.lf-logo {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.lf-server {
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.lw-btn {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 1px solid @white;
|
||||
display: inline-block;
|
||||
border-radius: 50%;
|
||||
font-size: 22px;
|
||||
color: @white;
|
||||
.transition(all);
|
||||
.transition-duration(300ms);
|
||||
opacity: 0.3;
|
||||
background-color: transparent;
|
||||
line-height: 45px;
|
||||
padding: 0;
|
||||
&:hover {
|
||||
color: @white;
|
||||
opacity: 0.8;
|
||||
border-color: @white;
|
||||
}
|
||||
|
||||
i {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding-left: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
/*------------------------------
|
||||
Chrome autofill fix
|
||||
-------------------------------*/
|
||||
input:-webkit-autofill {
|
||||
-webkit-box-shadow:0 0 0 50px @dark-gray inset !important;
|
||||
-webkit-text-fill-color: @white !important;
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*--------------------------
|
||||
Close
|
||||
----------------------------*/
|
||||
.close-variant(@color, @bg-color, @color-hover, @bg-color-hover) {
|
||||
span {
|
||||
background-color: @bg-color;
|
||||
color: @color;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
span {
|
||||
background-color: @bg-color-hover;
|
||||
color: @color-hover;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.close {
|
||||
right: 15px;
|
||||
font-weight: normal;
|
||||
opacity: 1;
|
||||
font-size: 18px;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
top: 16px;
|
||||
z-index: 1;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
|
||||
span {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
display: block;
|
||||
border-radius: 50%;
|
||||
line-height: 24px;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
&:not(.close-alt) {
|
||||
.close-variant(rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.1), @white, rgba(255, 255, 255, 0.2));
|
||||
}
|
||||
}
|
||||
|
||||
.close-alt {
|
||||
.close-variant(#989898, #efefef, #7b7b7b, #e8e8e8);
|
||||
}
|
||||
|
||||
|
||||
/*--------------------------
|
||||
Hidden
|
||||
----------------------------*/
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
/*--------------------------
|
||||
Copy text
|
||||
----------------------------*/
|
||||
.copy-text {
|
||||
input {
|
||||
width: 100%;
|
||||
border-radius: 1px;
|
||||
border: 1px solid @input-border;
|
||||
padding: 7px 12px;
|
||||
font-size: 13px;
|
||||
line-height: 100%;
|
||||
cursor: text;
|
||||
.transition(border-color);
|
||||
.transition-duration(300ms);
|
||||
|
||||
&:hover {
|
||||
border-color: darken(@input-border, 5%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*--------------------------
|
||||
Sharing
|
||||
----------------------------*/
|
||||
.share-availability {
|
||||
margin-bottom: 40px;
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
position: absolute;
|
||||
bottom: -30px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
&:before {
|
||||
content: '01 Sec';
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&:after {
|
||||
content: '7 days';
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*--------------------------
|
||||
User Select
|
||||
----------------------------*/
|
||||
.user-select(@value) {
|
||||
-webkit-user-select: @value;
|
||||
-moz-user-select: @value;
|
||||
-ms-user-select: @value;
|
||||
user-select: @value;
|
||||
}
|
||||
|
||||
|
||||
/*----------------------------------------
|
||||
CSS Animations based on animate.css
|
||||
-----------------------------------------*/
|
||||
.animated(@name, @duration) {
|
||||
-webkit-animation-name: @name;
|
||||
animation-name: @name;
|
||||
-webkit-animation-duration: @duration;
|
||||
animation-duration: @duration;
|
||||
-webkit-animation-fill-mode: both;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------
|
||||
For loop mixin for generate custom classes
|
||||
--------------------------------------------------*/
|
||||
.for(@i, @n) {.-each(@i)}
|
||||
.for(@n) when (isnumber(@n)) {.for(1, @n)}
|
||||
.for(@i, @n) when not (@i = @n) {
|
||||
.for((@i + (@n - @i) / abs(@n - @i)), @n);
|
||||
}
|
||||
|
||||
.for(@array) when (default()) {.for-impl_(length(@array))}
|
||||
.for-impl_(@i) when (@i > 1) {.for-impl_((@i - 1))}
|
||||
.for-impl_(@i) when (@i > 0) {.-each(extract(@array, @i))}
|
||||
|
||||
/*----------------------------------------
|
||||
List Loader
|
||||
-----------------------------------------*/
|
||||
.list-loader(@width, @height, @borderColor, @borderColorBottom) {
|
||||
content: '';
|
||||
width: @width;
|
||||
height: @height;
|
||||
border-radius: 50%;
|
||||
.animated(zoomIn, 500ms);
|
||||
border: 2px solid @borderColor;
|
||||
border-bottom-color: @borderColorBottom;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
-webkit-animation: zoomIn 250ms, spin 700ms 250ms infinite linear;
|
||||
animation: zoomIn 250ms, spin 700ms 250ms infinite linear;
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
/*--------------------------
|
||||
Modal
|
||||
----------------------------*/
|
||||
.modal {
|
||||
@media(min-width: @screen-sm-min) {
|
||||
text-align: center;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
height: 100%;
|
||||
width: 1px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
text-align: left;
|
||||
margin: 10px auto;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-dark {
|
||||
.modal-header {
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
|
||||
small {
|
||||
color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: @dark-gray;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-backdrop {
|
||||
.animated(fadeIn, 200ms);
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
.animated(zoomIn, 200ms);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
color: @text-strong-color;
|
||||
position: relative;
|
||||
|
||||
small {
|
||||
display: block;
|
||||
text-transform: none;
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
color: #a8a8a8;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
border-radius: 3px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding: 0 30px 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
/*--------------------------
|
||||
Dialog
|
||||
----------------------------*/
|
||||
.modal-confirm {
|
||||
.modal-dialog {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.mc-icon {
|
||||
margin: 0 0 10px;
|
||||
|
||||
& > i {
|
||||
font-size: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
.mci-red {
|
||||
color: #ff8f8f;
|
||||
}
|
||||
|
||||
.mci-amber {
|
||||
color: @amber;
|
||||
}
|
||||
|
||||
.mci-green {
|
||||
color: #64e096;
|
||||
}
|
||||
|
||||
.mc-text {
|
||||
color: @text-strong-color;
|
||||
}
|
||||
|
||||
.mc-sub {
|
||||
color: @text-muted-color;
|
||||
margin-top: 5px;
|
||||
font-size: 13px;
|
||||
}
|
||||
//--------------------------
|
||||
|
||||
|
||||
/*--------------------------
|
||||
About
|
||||
----------------------------*/
|
||||
.modal-about {
|
||||
@media (max-width: @screen-xs-max) {
|
||||
text-align: center;
|
||||
|
||||
.modal-dialog {
|
||||
max-width: 400px;
|
||||
width: 90%;
|
||||
margin: 20px auto 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ma-inner {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
min-height: 350px;
|
||||
position: relative;
|
||||
|
||||
@media (min-width: @screen-sm-min) {
|
||||
&:before {
|
||||
content: '';
|
||||
width: 150px;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
border-radius: 3px 0 0px 3px;
|
||||
background-color: #23282C;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mai-item {
|
||||
&:first-child {
|
||||
width: 150px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
flex: 4;
|
||||
padding: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.maii-logo {
|
||||
width: 70px;
|
||||
position: relative;
|
||||
|
||||
}
|
||||
|
||||
.maii-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
|
||||
& > li {
|
||||
margin-bottom: 15px;
|
||||
|
||||
div {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
text-transform: uppercase;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
}
|
||||
}
|
||||
//--------------------------
|
||||
|
||||
|
||||
/*--------------------------
|
||||
Preferences
|
||||
----------------------------*/
|
||||
.toggle-password {
|
||||
position: absolute;
|
||||
bottom: 30px;
|
||||
right: 35px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 0;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
z-index: 10;
|
||||
background-color: @white;
|
||||
padding-top: 5px;
|
||||
|
||||
&.toggled {
|
||||
background: #eee;
|
||||
}
|
||||
}
|
||||
//--------------------------
|
||||
|
||||
|
||||
/*--------------------------
|
||||
Policy
|
||||
----------------------------*/
|
||||
.pm-body {
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
|
||||
.pmb-header {
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
|
||||
.pmb-list {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px 35px;
|
||||
|
||||
&:nth-child(even) {
|
||||
background-color: #F7F7F7;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
header.pmb-list {
|
||||
margin: 20px 0 10px;
|
||||
}
|
||||
|
||||
.pmbl-item {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
|
||||
&:nth-child(1) {
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
margin: 0 25px;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
width: 70px;
|
||||
}
|
||||
}
|
||||
|
||||
div.pmb-list {
|
||||
select {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.pml-item {
|
||||
&:not(:last-child) {
|
||||
padding: 0 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
//--------------------------
|
||||
|
||||
|
||||
/*--------------------------
|
||||
Create Bucket
|
||||
----------------------------*/
|
||||
.modal-create-bucket {
|
||||
.modal-dialog {
|
||||
position: fixed;
|
||||
right: 25px;
|
||||
bottom: 95px;
|
||||
margin: 0;
|
||||
height: 110px;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
//--------------------------
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
/*--------------------------
|
||||
Sidebar
|
||||
----------------------------*/
|
||||
.fe-sidebar {
|
||||
width: @fe-sidebar-width;
|
||||
background-color: @dark-gray;
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
padding: 35px;
|
||||
|
||||
@media(min-width: @screen-md-min) {
|
||||
.translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
@media(max-width: @screen-sm-max) {
|
||||
padding-top: 85px;
|
||||
z-index: 9;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.65);
|
||||
.transition(all);
|
||||
.transition-duration(300ms);
|
||||
.translate3d((-@fe-sidebar-width - 15px), 0, 0);
|
||||
|
||||
&.toggled {
|
||||
.translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgba(255, 255, 255, 0.58);
|
||||
|
||||
&:hover {
|
||||
color: @white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*--------------------------
|
||||
Header
|
||||
----------------------------*/
|
||||
.fes-header {
|
||||
margin-bottom: 40px;
|
||||
|
||||
img,
|
||||
h2 {
|
||||
float: left;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 13px 0 0 10px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
/*--------------------------
|
||||
List
|
||||
----------------------------*/
|
||||
.fesl-inner {
|
||||
height: ~"calc(100vh - 260px)";
|
||||
overflow: auto;
|
||||
padding: 0;
|
||||
margin: 0 -35px;
|
||||
|
||||
& li {
|
||||
position: relative;
|
||||
|
||||
& > a {
|
||||
display: block;
|
||||
padding: 10px 40px 12px 65px;
|
||||
.text-overflow();
|
||||
|
||||
&:before {
|
||||
font-family: FontAwesome;
|
||||
content: '\f0a0';
|
||||
font-size: 17px;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 35px;
|
||||
.opacity(0.8);
|
||||
}
|
||||
|
||||
&.fesli-loading {
|
||||
&:before {
|
||||
.list-loader(20px, 20px, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.5));
|
||||
left: 32px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
|
||||
& > a {
|
||||
color: @white;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.active):hover {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
|
||||
& > a {
|
||||
color: @white;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.fesli-trigger {
|
||||
.opacity(0.6);
|
||||
|
||||
&:hover {
|
||||
.opacity(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&:hover .scrollbar-vertical {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.fesli-trigger {
|
||||
.opacity(0);
|
||||
.transition(all);
|
||||
.transition-duration(200ms);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 40px;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
background: url(../../img/more-h-light.svg) no-repeat left;
|
||||
}
|
||||
|
||||
/* Scrollbar */
|
||||
.scrollbar-vertical {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
width: 4px;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
.transition(opacity);
|
||||
.transition-duration(300ms);
|
||||
|
||||
div {
|
||||
border-radius: 1px !important;
|
||||
background-color: #6a6a6a !important;
|
||||
}
|
||||
}
|
||||
|
||||
/*--------------------------
|
||||
Host
|
||||
----------------------------*/
|
||||
.fes-host {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
background: @dark-gray;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
width: @fe-sidebar-width;
|
||||
padding: 20px;
|
||||
.text-overflow();
|
||||
|
||||
& > i {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/*--------------------------
|
||||
Base
|
||||
----------------------------*/
|
||||
@font-family-sans-serif : 'Lato', sans-serif;
|
||||
@font-family-icon : 'fontAwesome';
|
||||
@body-bg : #edecec;
|
||||
@text-color : #8e8e8e;
|
||||
@font-size-base : 15px;
|
||||
@link-color : #46a5e0;
|
||||
@link-hover-decoration : none;
|
||||
|
||||
|
||||
/*--------------------------
|
||||
File Explorer
|
||||
----------------------------*/
|
||||
@fe-sidebar-width : 300px;
|
||||
@text-muted-color : #BDBDBD;
|
||||
@text-strong-color : #333;
|
||||
|
||||
/*--------------------------
|
||||
Colors
|
||||
----------------------------*/
|
||||
@cyan : #2ED2FF;
|
||||
@amber : #ffc107;
|
||||
@red : #ff726f;
|
||||
@grey : #f5f5f5;
|
||||
@dark-blue : #0084d3;
|
||||
@blue : #00a6f7;
|
||||
@white : #ffffff;
|
||||
@black : #1b1e25;
|
||||
@blue : #50b2ff;
|
||||
@light-blue : #c1d1e8;
|
||||
@green : #33d46f;
|
||||
@yellow : #FFC107;
|
||||
@orange : #ffc155;
|
||||
@purple : #9C27B0;
|
||||
@teal : #009688;
|
||||
@brown : #795548;
|
||||
@blue-gray : #374952;
|
||||
@dark-gray : #32393F;
|
||||
|
||||
|
||||
/*--------------------------
|
||||
Dropdown
|
||||
----------------------------*/
|
||||
@dropdown-fallback-border : transparent;
|
||||
@dropdown-border : transparent;
|
||||
@dropdown-divider-bg : '';
|
||||
@dropdown-link-hover-bg : rgba(0,0,0,0.05);
|
||||
@dropdown-link-color : @text-color;
|
||||
@dropdown-link-hover-color : #333;
|
||||
@dropdown-link-disabled-color : #e4e4e4;
|
||||
@dropdown-divider-bg : rgba(0,0,0,0.08);
|
||||
@dropdown-link-active-color : #333;
|
||||
@dropdown-link-active-bg : rgba(0, 0, 0, 0.075);
|
||||
@dropdown-shadow : 0 2px 10px rgba(0, 0, 0, 0.2);
|
||||
|
||||
|
||||
/*--------------------------
|
||||
Modal
|
||||
----------------------------*/
|
||||
@modal-content-fallback-border-color: transparent;
|
||||
@modal-content-border-color: transparent;
|
||||
@modal-backdrop-bg: rgba(0,0,0,0.1);
|
||||
@modal-header-border-color: transparent;
|
||||
@modal-title-line-height: transparent;
|
||||
@modal-footer-border-color: transparent;
|
||||
@modal-inner-padding: 30px 35px;
|
||||
@modal-title-padding: 30px 35px 0px;
|
||||
@modal-sm: 400px;
|
||||
|
||||
|
||||
/*-------------------------
|
||||
Buttons
|
||||
--------------------------*/
|
||||
@btn-border-radius-large: 2px;
|
||||
@btn-border-radius-small: 2px;
|
||||
@btn-border-radius-base: 2px;
|
||||
|
||||
|
||||
/*-------------------------
|
||||
Colors
|
||||
--------------------------*/
|
||||
@brand-primary: #2196F3;
|
||||
@brand-success: #4CAF50;
|
||||
@brand-info: #00BCD4;
|
||||
@brand-warning: #FF9800;
|
||||
@brand-danger: #FF5722;
|
||||
|
||||
|
||||
/*-------------------------
|
||||
Form
|
||||
--------------------------*/
|
||||
@input-border: #eee;
|
||||
@@ -0,0 +1,39 @@
|
||||
/*----------------------------
|
||||
Bootstrap
|
||||
-----------------------------*/
|
||||
@import "../../node_modules/bootstrap/less/scaffolding.less";
|
||||
@import "../../node_modules/bootstrap/less/variables.less";
|
||||
@import "../../node_modules/bootstrap/less/grid.less";
|
||||
@import "../../node_modules/bootstrap/less/mixins.less";
|
||||
@import "../../node_modules/bootstrap/less/normalize.less";
|
||||
@import "../../node_modules/bootstrap/less/dropdowns.less";
|
||||
@import "../../node_modules/bootstrap/less/modals.less";
|
||||
@import "../../node_modules/bootstrap/less/tooltip.less";
|
||||
@import "../../node_modules/bootstrap/less/responsive-utilities.less";
|
||||
|
||||
|
||||
/*----------------------------
|
||||
App
|
||||
-----------------------------*/
|
||||
@import 'inc/mixin';
|
||||
@import 'inc/variables';
|
||||
@import 'inc/base';
|
||||
@import 'inc/animate/animate';
|
||||
@import 'inc/generics';
|
||||
@import 'inc/font';
|
||||
@import 'inc/form';
|
||||
@import 'inc/buttons';
|
||||
@import 'inc/misc';
|
||||
@import 'inc/login';
|
||||
@import 'inc/header';
|
||||
@import 'inc/sidebar';
|
||||
@import 'inc/list';
|
||||
@import 'inc/file-explorer';
|
||||
@import 'inc/ie-warning';
|
||||
|
||||
/*----------------------------
|
||||
Boostrap
|
||||
-----------------------------*/
|
||||
@import 'inc/dropdown';
|
||||
@import 'inc/alert';
|
||||
@import 'inc/modal';
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var moment = require('moment')
|
||||
var async = require('async')
|
||||
var exec = require('child_process').exec
|
||||
var fs = require('fs')
|
||||
|
||||
var isProduction = process.env.NODE_ENV == 'production' ? true : false
|
||||
var assetsFileName = ''
|
||||
var commitId = ''
|
||||
var date = moment.utc()
|
||||
var version = date.format('YYYY-MM-DDTHH:mm:ss') + 'Z'
|
||||
var releaseTag = date.format('YYYY-MM-DDTHH-mm-ss') + 'Z'
|
||||
var buildType = 'DEVELOPMENT'
|
||||
if (process.env.MINIO_UI_BUILD) buildType = process.env.MINIO_UI_BUILD
|
||||
|
||||
rmDir = function(dirPath) {
|
||||
try { var files = fs.readdirSync(dirPath); }
|
||||
catch(e) { return; }
|
||||
if (files.length > 0)
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
var filePath = dirPath + '/' + files[i];
|
||||
if (fs.statSync(filePath).isFile())
|
||||
fs.unlinkSync(filePath);
|
||||
else
|
||||
rmDir(filePath);
|
||||
}
|
||||
fs.rmdirSync(dirPath);
|
||||
};
|
||||
|
||||
async.waterfall([
|
||||
function(cb) {
|
||||
rmDir('production');
|
||||
rmDir('dev');
|
||||
var cmd = 'webpack -p --config webpack.production.config.js'
|
||||
if (!isProduction) {
|
||||
cmd = 'webpack';
|
||||
}
|
||||
console.log('Running', cmd)
|
||||
exec(cmd, cb)
|
||||
},
|
||||
function(stdout, stderr, cb) {
|
||||
if (isProduction) {
|
||||
fs.renameSync('production/index_bundle.js',
|
||||
'production/index_bundle-' + releaseTag + '.js')
|
||||
} else {
|
||||
fs.renameSync('dev/index_bundle.js',
|
||||
'dev/index_bundle-' + releaseTag + '.js')
|
||||
}
|
||||
var cmd = 'git log --format="%H" -n1'
|
||||
console.log('Running', cmd)
|
||||
exec(cmd, cb)
|
||||
},
|
||||
function(stdout, stderr, cb) {
|
||||
if (!stdout) throw new Error('commitId is empty')
|
||||
commitId = stdout.replace('\n', '')
|
||||
if (commitId.length !== 40) throw new Error('commitId invalid : ' + commitId)
|
||||
assetsFileName = 'ui-assets.go';
|
||||
var cmd = 'go-bindata-assetfs -pkg miniobrowser -nocompress=true production/...'
|
||||
if (!isProduction) {
|
||||
cmd = 'go-bindata-assetfs -pkg miniobrowser -nocompress=true dev/...'
|
||||
}
|
||||
console.log('Running', cmd)
|
||||
exec(cmd, cb)
|
||||
},
|
||||
function(stdout, stderr, cb) {
|
||||
var cmd = 'gofmt -s -w -l bindata_assetfs.go'
|
||||
console.log('Running', cmd)
|
||||
exec(cmd, cb)
|
||||
},
|
||||
function(stdout, stderr, cb) {
|
||||
fs.renameSync('bindata_assetfs.go', assetsFileName)
|
||||
fs.appendFileSync(assetsFileName, '\n')
|
||||
fs.appendFileSync(assetsFileName, 'var UIReleaseTag = "' + buildType + '.' +
|
||||
releaseTag + '"\n')
|
||||
fs.appendFileSync(assetsFileName, 'var UICommitID = "' + commitId + '"\n')
|
||||
fs.appendFileSync(assetsFileName, 'var UIVersion = "' + version + '"')
|
||||
fs.appendFileSync(assetsFileName, '\n')
|
||||
var contents;
|
||||
if (isProduction) {
|
||||
contents = fs.readFileSync(assetsFileName, 'utf8')
|
||||
.replace(/_productionIndexHtml/g, '_productionIndexHTML')
|
||||
.replace(/productionIndexHtmlBytes/g, 'productionIndexHTMLBytes')
|
||||
.replace(/productionIndexHtml/g, 'productionIndexHTML')
|
||||
.replace(/_productionIndex_bundleJs/g, '_productionIndexBundleJs')
|
||||
.replace(/productionIndex_bundleJsBytes/g, 'productionIndexBundleJsBytes')
|
||||
.replace(/productionIndex_bundleJs/g, 'productionIndexBundleJs')
|
||||
.replace(/_productionJqueryUiMinJs/g, '_productionJqueryUIMinJs')
|
||||
.replace(/productionJqueryUiMinJsBytes/g, 'productionJqueryUIMinJsBytes')
|
||||
.replace(/productionJqueryUiMinJs/g, 'productionJqueryUIMinJs');
|
||||
} else {
|
||||
contents = fs.readFileSync(assetsFileName, 'utf8')
|
||||
.replace(/_devIndexHtml/g, '_devIndexHTML')
|
||||
.replace(/devIndexHtmlBytes/g, 'devIndexHTMLBytes')
|
||||
.replace(/devIndexHtml/g, 'devIndexHTML')
|
||||
.replace(/_devIndex_bundleJs/g, '_devIndexBundleJs')
|
||||
.replace(/devIndex_bundleJsBytes/g, 'devIndexBundleJsBytes')
|
||||
.replace(/devIndex_bundleJs/g, 'devIndexBundleJs')
|
||||
.replace(/_devJqueryUiMinJs/g, '_devJqueryUIMinJs')
|
||||
.replace(/devJqueryUiMinJsBytes/g, 'devJqueryUIMinJsBytes')
|
||||
.replace(/devJqueryUiMinJs/g, 'devJqueryUIMinJs');
|
||||
}
|
||||
contents = contents.replace(/MINIO_UI_VERSION/g, version)
|
||||
contents = contents.replace(/index_bundle.js/g, 'index_bundle-' + releaseTag + '.js')
|
||||
|
||||
fs.writeFileSync(assetsFileName, contents, 'utf8')
|
||||
console.log('UI assets file :', assetsFileName)
|
||||
cb()
|
||||
}
|
||||
], function(err) {
|
||||
if (err) return console.log(err)
|
||||
})
|
||||
@@ -0,0 +1,40 @@
|
||||
var webpack = require('webpack');
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
browsers: [ process.env.CONTINUOUS_INTEGRATION ? 'Firefox' : 'Chrome' ],
|
||||
singleRun: true,
|
||||
frameworks: [ 'mocha' ],
|
||||
files: [
|
||||
'tests.webpack.js'
|
||||
],
|
||||
preprocessors: {
|
||||
'tests.webpack.js': [ 'webpack' ]
|
||||
},
|
||||
reporters: [ 'dots' ],
|
||||
webpack: {
|
||||
module: {
|
||||
loaders: [{
|
||||
test: /\.js$/,
|
||||
exclude: /(node_modules|bower_components)/,
|
||||
loader: 'babel',
|
||||
query: {
|
||||
presets: ['react', 'es2015']
|
||||
}
|
||||
}, {
|
||||
test: /\.less$/,
|
||||
loader: 'style!css!less'
|
||||
}, {
|
||||
test: /\.css$/,
|
||||
loader: 'style!css'
|
||||
}, {
|
||||
test: /\.(eot|woff|woff2|ttf|svg|png)/,
|
||||
loader: 'url'
|
||||
}]
|
||||
}
|
||||
},
|
||||
webpackServer: {
|
||||
noInfo: true
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"name": "minio-browser",
|
||||
"version": "0.0.1",
|
||||
"description": "Minio Browser",
|
||||
"scripts": {
|
||||
"test": "karma start",
|
||||
"dev": "NODE_ENV=dev webpack-dev-server --devtool eval --progress --colors --hot --content-base dev",
|
||||
"build": "NODE_ENV=dev node build.js",
|
||||
"release": "NODE_ENV=production MINIO_UI_BUILD=RELEASE node build.js",
|
||||
"format": "esformatter -i 'app/**/*.js'"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/minio/miniobrowser"
|
||||
},
|
||||
"author": "Minio Inc",
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/minio/miniobrowser/issues"
|
||||
},
|
||||
"homepage": "https://github.com/minio/miniobrowser",
|
||||
"devDependencies": {
|
||||
"async": "^1.5.2",
|
||||
"babel-cli": "^6.14.0",
|
||||
"babel-core": "^6.14.0",
|
||||
"babel-loader": "^6.2.5",
|
||||
"babel-plugin-syntax-object-rest-spread": "^6.13.0",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.8.0",
|
||||
"babel-preset-es2015": "^6.14.0",
|
||||
"babel-preset-react": "^6.11.1",
|
||||
"babel-register": "^6.14.0",
|
||||
"copy-webpack-plugin": "^0.3.3",
|
||||
"css-loader": "^0.23.1",
|
||||
"esformatter": "^0.10.0",
|
||||
"esformatter-jsx": "^7.4.1",
|
||||
"esformatter-jsx-ignore": "^1.0.6",
|
||||
"expect": "^1.20.2",
|
||||
"history": "^1.17.0",
|
||||
"html-webpack-plugin": "^2.22.0",
|
||||
"json-loader": "^0.5.4",
|
||||
"karma": "^0.13.22",
|
||||
"karma-chrome-launcher": "^0.2.3",
|
||||
"karma-cli": "^0.1.2",
|
||||
"karma-firefox-launcher": "^0.1.7",
|
||||
"karma-mocha": "^0.2.2",
|
||||
"karma-webpack": "^1.7.0",
|
||||
"less": "^2.7.1",
|
||||
"less-loader": "^2.2.3",
|
||||
"mocha": "^2.5.3",
|
||||
"moment": "^2.15.1",
|
||||
"purifycss-webpack-plugin": "^2.0.3",
|
||||
"react": "^0.14.8",
|
||||
"react-addons-test-utils": "^0.14.8",
|
||||
"react-bootstrap": "^0.28.5",
|
||||
"react-custom-scrollbars": "^2.3.0",
|
||||
"react-redux": "^4.4.5",
|
||||
"react-router": "^2.8.1",
|
||||
"redux": "^3.6.0",
|
||||
"redux-thunk": "^1.0.3",
|
||||
"style-loader": "^0.13.1",
|
||||
"superagent": "^1.8.4",
|
||||
"superagent-es6-promise": "^1.0.0",
|
||||
"url-loader": "^0.5.7",
|
||||
"webpack": "^1.12.11",
|
||||
"webpack-dev-server": "^1.14.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"bootstrap": "^3.3.6",
|
||||
"classnames": "^2.2.3",
|
||||
"font-awesome": "^4.7.0",
|
||||
"humanize": "0.0.9",
|
||||
"json-loader": "^0.5.4",
|
||||
"local-storage-fallback": "^1.3.0",
|
||||
"mime-db": "^1.25.0",
|
||||
"mime-types": "^2.1.13",
|
||||
"react": "^0.14.8",
|
||||
"react-copy-to-clipboard": "^4.2.3",
|
||||
"react-custom-scrollbars": "^2.2.2",
|
||||
"react-dom": "^0.14.6",
|
||||
"react-dropzone": "^3.5.3",
|
||||
"react-infinite-scroller": "^1.0.6",
|
||||
"react-onclickout": "2.0.4"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
var context = require.context('./app', true, /-test\.js$/);
|
||||
context.keys().forEach(context);
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Minio Browser (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var webpack = require('webpack')
|
||||
var path = require('path')
|
||||
var CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
var purify = require("purifycss-webpack-plugin")
|
||||
|
||||
var exports = {
|
||||
context: __dirname,
|
||||
entry: [
|
||||
path.resolve(__dirname, 'app/index.js')
|
||||
],
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dev'),
|
||||
filename: 'index_bundle.js',
|
||||
publicPath: '/minio/'
|
||||
},
|
||||
module: {
|
||||
loaders: [{
|
||||
test: /\.js$/,
|
||||
exclude: /(node_modules|bower_components)/,
|
||||
loader: 'babel',
|
||||
query: {
|
||||
presets: ['react', 'es2015']
|
||||
}
|
||||
}, {
|
||||
test: /\.less$/,
|
||||
loader: 'style!css!less'
|
||||
}, {
|
||||
test: /\.json$/,
|
||||
loader: 'json-loader'
|
||||
},{
|
||||
test: /\.css$/,
|
||||
loader: 'style!css'
|
||||
}, {
|
||||
test: /\.(eot|woff|woff2|ttf|svg|png)/,
|
||||
loader: 'url'
|
||||
}]
|
||||
},
|
||||
node:{
|
||||
fs:'empty'
|
||||
},
|
||||
devServer: {
|
||||
historyApiFallback: {
|
||||
index: '/minio/'
|
||||
},
|
||||
proxy: {
|
||||
'/minio/webrpc': {
|
||||
target: 'http://localhost:9000',
|
||||
secure: false
|
||||
},
|
||||
'/minio/upload/*': {
|
||||
target: 'http://localhost:9000',
|
||||
secure: false
|
||||
},
|
||||
'/minio/download/*': {
|
||||
target: 'http://localhost:9000',
|
||||
secure: false
|
||||
},
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
new CopyWebpackPlugin([
|
||||
{from: 'app/css/loader.css'},
|
||||
{from: 'app/img/favicon.ico'},
|
||||
{from: 'app/img/browsers/chrome.png'},
|
||||
{from: 'app/img/browsers/firefox.png'},
|
||||
{from: 'app/img/browsers/safari.png'},
|
||||
{from: 'app/img/logo.svg'},
|
||||
{from: 'app/index.html'}
|
||||
]),
|
||||
new webpack.ContextReplacementPlugin(/moment[\\\/]locale$/, /^\.\/(en)$/),
|
||||
new purify({
|
||||
basePath: __dirname,
|
||||
paths: [
|
||||
"app/index.html",
|
||||
"app/js/*.js"
|
||||
]
|
||||
})
|
||||
]
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'dev') {
|
||||
exports.entry = [
|
||||
'webpack/hot/dev-server',
|
||||
'webpack-dev-server/client?http://localhost:8080',
|
||||
path.resolve(__dirname, 'app/index.js')
|
||||
]
|
||||
}
|
||||
|
||||
module.exports = exports
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Isomorphic Javascript library for Minio Browser JSON-RPC API, (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var webpack = require('webpack')
|
||||
var path = require('path')
|
||||
var CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
var purify = require("purifycss-webpack-plugin")
|
||||
|
||||
var exports = {
|
||||
context: __dirname,
|
||||
entry: [
|
||||
path.resolve(__dirname, 'app/index.js')
|
||||
],
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'production'),
|
||||
filename: 'index_bundle.js'
|
||||
},
|
||||
module: {
|
||||
loaders: [{
|
||||
test: /\.js$/,
|
||||
exclude: /(node_modules|bower_components)/,
|
||||
loader: 'babel',
|
||||
query: {
|
||||
presets: ['react', 'es2015']
|
||||
}
|
||||
}, {
|
||||
test: /\.less$/,
|
||||
loader: 'style!css!less'
|
||||
}, {
|
||||
test: /\.json$/,
|
||||
loader: 'json-loader'
|
||||
}, {
|
||||
test: /\.css$/,
|
||||
loader: 'style!css'
|
||||
}, {
|
||||
test: /\.(eot|woff|woff2|ttf|svg|png)/,
|
||||
loader: 'url'
|
||||
}]
|
||||
},
|
||||
node:{
|
||||
fs:'empty'
|
||||
},
|
||||
plugins: [
|
||||
new CopyWebpackPlugin([
|
||||
{from: 'app/css/loader.css'},
|
||||
{from: 'app/img/favicon.ico'},
|
||||
{from: 'app/img/browsers/chrome.png'},
|
||||
{from: 'app/img/browsers/firefox.png'},
|
||||
{from: 'app/img/browsers/safari.png'},
|
||||
{from: 'app/img/logo.svg'},
|
||||
{from: 'app/index.html'}
|
||||
]),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': '"production"'
|
||||
}),
|
||||
new webpack.ContextReplacementPlugin(/moment[\\\/]locale$/, /^\.\/(en)$/),
|
||||
new purify({
|
||||
basePath: __dirname,
|
||||
paths: [
|
||||
"app/index.html",
|
||||
"app/js/*.js"
|
||||
]
|
||||
})
|
||||
]
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'dev') {
|
||||
exports.entry = [
|
||||
'webpack/hot/dev-server',
|
||||
'webpack-dev-server/client?http://localhost:8080',
|
||||
path.resolve(__dirname, 'app/index.js')
|
||||
]
|
||||
}
|
||||
|
||||
module.exports = exports
|
||||
@@ -1,447 +0,0 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
mux "github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// http://docs.aws.amazon.com/AmazonS3/latest/dev/using-with-s3-actions.html
|
||||
// Enforces bucket policies for a bucket for a given tatusaction.
|
||||
func enforceBucketPolicy(bucket string, action string, reqURL *url.URL) (s3Error APIErrorCode) {
|
||||
if !IsValidBucketName(bucket) {
|
||||
return ErrInvalidBucketName
|
||||
}
|
||||
// Fetch bucket policy, if policy is not set return access denied.
|
||||
policy := globalBucketPolicies.GetBucketPolicy(bucket)
|
||||
if policy == nil {
|
||||
return ErrAccessDenied
|
||||
}
|
||||
|
||||
// Construct resource in 'arn:aws:s3:::examplebucket/object' format.
|
||||
resource := AWSResourcePrefix + strings.TrimPrefix(reqURL.Path, "/")
|
||||
|
||||
// Get conditions for policy verification.
|
||||
conditions := make(map[string]string)
|
||||
for queryParam := range reqURL.Query() {
|
||||
conditions[queryParam] = reqURL.Query().Get(queryParam)
|
||||
}
|
||||
|
||||
// Validate action, resource and conditions with current policy statements.
|
||||
if !bucketPolicyEvalStatements(action, resource, conditions, policy.Statements) {
|
||||
return ErrAccessDenied
|
||||
}
|
||||
return ErrNone
|
||||
}
|
||||
|
||||
// GetBucketLocationHandler - GET Bucket location.
|
||||
// -------------------------
|
||||
// This operation returns bucket location.
|
||||
func (api objectAPIHandlers) GetBucketLocationHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
bucket := vars["bucket"]
|
||||
|
||||
switch getRequestAuthType(r) {
|
||||
default:
|
||||
// For all unknown auth types return error.
|
||||
writeErrorResponse(w, r, ErrAccessDenied, r.URL.Path)
|
||||
return
|
||||
case authTypeAnonymous:
|
||||
// http://docs.aws.amazon.com/AmazonS3/latest/dev/using-with-s3-actions.html
|
||||
if s3Error := enforceBucketPolicy(bucket, "s3:GetBucketLocation", r.URL); s3Error != ErrNone {
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
case authTypeSigned, authTypePresigned:
|
||||
if s3Error := isReqAuthenticated(r); s3Error != ErrNone {
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := api.ObjectAPI.GetBucketInfo(bucket); err != nil {
|
||||
errorIf(err, "Unable to fetch bucket info.")
|
||||
writeErrorResponse(w, r, toAPIErrorCode(err), r.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
// Generate response.
|
||||
encodedSuccessResponse := encodeResponse(LocationResponse{})
|
||||
// Get current region.
|
||||
region := serverConfig.GetRegion()
|
||||
if region != "us-east-1" {
|
||||
encodedSuccessResponse = encodeResponse(LocationResponse{
|
||||
Location: region,
|
||||
})
|
||||
}
|
||||
setCommonHeaders(w) // Write headers.
|
||||
writeSuccessResponse(w, encodedSuccessResponse)
|
||||
}
|
||||
|
||||
// ListMultipartUploadsHandler - GET Bucket (List Multipart uploads)
|
||||
// -------------------------
|
||||
// This operation lists in-progress multipart uploads. An in-progress
|
||||
// multipart upload is a multipart upload that has been initiated,
|
||||
// using the Initiate Multipart Upload request, but has not yet been
|
||||
// completed or aborted. This operation returns at most 1,000 multipart
|
||||
// uploads in the response.
|
||||
//
|
||||
func (api objectAPIHandlers) ListMultipartUploadsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
bucket := vars["bucket"]
|
||||
|
||||
switch getRequestAuthType(r) {
|
||||
default:
|
||||
// For all unknown auth types return error.
|
||||
writeErrorResponse(w, r, ErrAccessDenied, r.URL.Path)
|
||||
return
|
||||
case authTypeAnonymous:
|
||||
// http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html
|
||||
if s3Error := enforceBucketPolicy(bucket, "s3:ListBucketMultipartUploads", r.URL); s3Error != ErrNone {
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
case authTypePresigned, authTypeSigned:
|
||||
if s3Error := isReqAuthenticated(r); s3Error != ErrNone {
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
prefix, keyMarker, uploadIDMarker, delimiter, maxUploads, _ := getBucketMultipartResources(r.URL.Query())
|
||||
if maxUploads < 0 {
|
||||
writeErrorResponse(w, r, ErrInvalidMaxUploads, r.URL.Path)
|
||||
return
|
||||
}
|
||||
if keyMarker != "" {
|
||||
// Marker not common with prefix is not implemented.
|
||||
if !strings.HasPrefix(keyMarker, prefix) {
|
||||
writeErrorResponse(w, r, ErrNotImplemented, r.URL.Path)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
listMultipartsInfo, err := api.ObjectAPI.ListMultipartUploads(bucket, prefix, keyMarker, uploadIDMarker, delimiter, maxUploads)
|
||||
if err != nil {
|
||||
errorIf(err, "Unable to list multipart uploads.")
|
||||
writeErrorResponse(w, r, toAPIErrorCode(err), r.URL.Path)
|
||||
return
|
||||
}
|
||||
// generate response
|
||||
response := generateListMultipartUploadsResponse(bucket, listMultipartsInfo)
|
||||
encodedSuccessResponse := encodeResponse(response)
|
||||
// write headers.
|
||||
setCommonHeaders(w)
|
||||
// write success response.
|
||||
writeSuccessResponse(w, encodedSuccessResponse)
|
||||
}
|
||||
|
||||
// ListBucketsHandler - GET Service
|
||||
// -----------
|
||||
// This implementation of the GET operation returns a list of all buckets
|
||||
// owned by the authenticated sender of the request.
|
||||
func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// List buckets does not support bucket policies, no need to enforce it.
|
||||
if s3Error := checkAuth(r); s3Error != ErrNone {
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
bucketsInfo, err := api.ObjectAPI.ListBuckets()
|
||||
if err != nil {
|
||||
errorIf(err, "Unable to list buckets.")
|
||||
writeErrorResponse(w, r, toAPIErrorCode(err), r.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
// Generate response.
|
||||
response := generateListBucketsResponse(bucketsInfo)
|
||||
encodedSuccessResponse := encodeResponse(response)
|
||||
// Write headers.
|
||||
setCommonHeaders(w)
|
||||
// Write response.
|
||||
writeSuccessResponse(w, encodedSuccessResponse)
|
||||
}
|
||||
|
||||
// DeleteMultipleObjectsHandler - deletes multiple objects.
|
||||
func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
bucket := vars["bucket"]
|
||||
|
||||
switch getRequestAuthType(r) {
|
||||
default:
|
||||
// For all unknown auth types return error.
|
||||
writeErrorResponse(w, r, ErrAccessDenied, r.URL.Path)
|
||||
return
|
||||
case authTypeAnonymous:
|
||||
// http://docs.aws.amazon.com/AmazonS3/latest/dev/using-with-s3-actions.html
|
||||
if s3Error := enforceBucketPolicy(bucket, "s3:DeleteObject", r.URL); s3Error != ErrNone {
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
case authTypePresigned, authTypeSigned:
|
||||
if s3Error := isReqAuthenticated(r); s3Error != ErrNone {
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Content-Length is required and should be non-zero
|
||||
// http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html
|
||||
if r.ContentLength <= 0 {
|
||||
writeErrorResponse(w, r, ErrMissingContentLength, r.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
// Content-Md5 is requied should be set
|
||||
// http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html
|
||||
if _, ok := r.Header["Content-Md5"]; !ok {
|
||||
writeErrorResponse(w, r, ErrMissingContentMD5, r.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
// Allocate incoming content length bytes.
|
||||
deleteXMLBytes := make([]byte, r.ContentLength)
|
||||
|
||||
// Read incoming body XML bytes.
|
||||
if _, err := io.ReadFull(r.Body, deleteXMLBytes); err != nil {
|
||||
errorIf(err, "Unable to read HTTP body.")
|
||||
writeErrorResponse(w, r, ErrInternalError, r.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
// Unmarshal list of keys to be deleted.
|
||||
deleteObjects := &DeleteObjectsRequest{}
|
||||
if err := xml.Unmarshal(deleteXMLBytes, deleteObjects); err != nil {
|
||||
errorIf(err, "Unable to unmarshal delete objects request XML.")
|
||||
writeErrorResponse(w, r, ErrMalformedXML, r.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
var deleteErrors []DeleteError
|
||||
var deletedObjects []ObjectIdentifier
|
||||
// Loop through all the objects and delete them sequentially.
|
||||
for _, object := range deleteObjects.Objects {
|
||||
err := api.ObjectAPI.DeleteObject(bucket, object.ObjectName)
|
||||
if err == nil {
|
||||
deletedObjects = append(deletedObjects, ObjectIdentifier{
|
||||
ObjectName: object.ObjectName,
|
||||
})
|
||||
} else {
|
||||
errorIf(err, "Unable to delete object.")
|
||||
deleteErrors = append(deleteErrors, DeleteError{
|
||||
Code: errorCodeResponse[toAPIErrorCode(err)].Code,
|
||||
Message: errorCodeResponse[toAPIErrorCode(err)].Description,
|
||||
Key: object.ObjectName,
|
||||
})
|
||||
}
|
||||
}
|
||||
// Generate response
|
||||
response := generateMultiDeleteResponse(deleteObjects.Quiet, deletedObjects, deleteErrors)
|
||||
encodedSuccessResponse := encodeResponse(response)
|
||||
// Write headers
|
||||
setCommonHeaders(w)
|
||||
// Write success response.
|
||||
writeSuccessResponse(w, encodedSuccessResponse)
|
||||
}
|
||||
|
||||
// PutBucketHandler - PUT Bucket
|
||||
// ----------
|
||||
// This implementation of the PUT operation creates a new bucket for authenticated request
|
||||
func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// PutBucket does not support policies, use checkAuth to validate signature.
|
||||
if s3Error := checkAuth(r); s3Error != ErrNone {
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
bucket := vars["bucket"]
|
||||
|
||||
// Validate if incoming location constraint is valid, reject
|
||||
// requests which do not follow valid region requirements.
|
||||
if s3Error := isValidLocationConstraint(r); s3Error != ErrNone {
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
}
|
||||
|
||||
// Proceed to creating a bucket.
|
||||
err := api.ObjectAPI.MakeBucket(bucket)
|
||||
if err != nil {
|
||||
errorIf(err, "Unable to create a bucket.")
|
||||
writeErrorResponse(w, r, toAPIErrorCode(err), r.URL.Path)
|
||||
return
|
||||
}
|
||||
// Make sure to add Location information here only for bucket
|
||||
w.Header().Set("Location", getLocation(r))
|
||||
writeSuccessResponse(w, nil)
|
||||
}
|
||||
|
||||
// PostPolicyBucketHandler - POST policy
|
||||
// ----------
|
||||
// This implementation of the POST operation handles object creation with a specified
|
||||
// signature policy in multipart/form-data
|
||||
func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Here the parameter is the size of the form data that should
|
||||
// be loaded in memory, the remaining being put in temporary files.
|
||||
reader, err := r.MultipartReader()
|
||||
if err != nil {
|
||||
errorIf(err, "Unable to initialize multipart reader.")
|
||||
writeErrorResponse(w, r, ErrMalformedPOSTRequest, r.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
fileBody, fileName, formValues, err := extractPostPolicyFormValues(reader)
|
||||
if err != nil {
|
||||
errorIf(err, "Unable to parse form values.")
|
||||
writeErrorResponse(w, r, ErrMalformedPOSTRequest, r.URL.Path)
|
||||
return
|
||||
}
|
||||
bucket := mux.Vars(r)["bucket"]
|
||||
formValues["Bucket"] = bucket
|
||||
object := formValues["Key"]
|
||||
|
||||
if fileName != "" && strings.Contains(object, "${filename}") {
|
||||
// S3 feature to replace ${filename} found in Key form field
|
||||
// by the filename attribute passed in multipart
|
||||
object = strings.Replace(object, "${filename}", fileName, -1)
|
||||
}
|
||||
|
||||
// Verify policy signature.
|
||||
apiErr := doesPolicySignatureMatch(formValues)
|
||||
if apiErr != ErrNone {
|
||||
writeErrorResponse(w, r, apiErr, r.URL.Path)
|
||||
return
|
||||
}
|
||||
if apiErr = checkPostPolicy(formValues); apiErr != ErrNone {
|
||||
writeErrorResponse(w, r, apiErr, r.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
// Save metadata.
|
||||
metadata := make(map[string]string)
|
||||
// Nothing to store right now.
|
||||
|
||||
md5Sum, err := api.ObjectAPI.PutObject(bucket, object, -1, fileBody, metadata)
|
||||
if err != nil {
|
||||
errorIf(err, "Unable to create object.")
|
||||
writeErrorResponse(w, r, toAPIErrorCode(err), r.URL.Path)
|
||||
return
|
||||
}
|
||||
if md5Sum != "" {
|
||||
w.Header().Set("ETag", "\""+md5Sum+"\"")
|
||||
}
|
||||
encodedSuccessResponse := encodeResponse(PostResponse{
|
||||
Location: getObjectLocation(bucket, object), // TODO Full URL is preferred
|
||||
Bucket: bucket,
|
||||
Key: object,
|
||||
ETag: md5Sum,
|
||||
})
|
||||
|
||||
setCommonHeaders(w)
|
||||
|
||||
writeSuccessResponse(w, encodedSuccessResponse)
|
||||
|
||||
// Fetch object info for notifications.
|
||||
objInfo, err := api.ObjectAPI.GetObjectInfo(bucket, object)
|
||||
if err != nil {
|
||||
errorIf(err, "Unable to fetch object info for \"%s\"", path.Join(bucket, object))
|
||||
return
|
||||
}
|
||||
|
||||
if eventN.IsBucketNotificationSet(bucket) {
|
||||
// Notify object created event.
|
||||
eventNotify(eventData{
|
||||
Type: ObjectCreatedPost,
|
||||
Bucket: bucket,
|
||||
ObjInfo: objInfo,
|
||||
ReqParams: map[string]string{
|
||||
"sourceIPAddress": r.RemoteAddr,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// HeadBucketHandler - HEAD Bucket
|
||||
// ----------
|
||||
// This operation is useful to determine if a bucket exists.
|
||||
// The operation returns a 200 OK if the bucket exists and you
|
||||
// have permission to access it. Otherwise, the operation might
|
||||
// return responses such as 404 Not Found and 403 Forbidden.
|
||||
func (api objectAPIHandlers) HeadBucketHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
bucket := vars["bucket"]
|
||||
|
||||
switch getRequestAuthType(r) {
|
||||
default:
|
||||
// For all unknown auth types return error.
|
||||
writeErrorResponse(w, r, ErrAccessDenied, r.URL.Path)
|
||||
return
|
||||
case authTypeAnonymous:
|
||||
// http://docs.aws.amazon.com/AmazonS3/latest/dev/using-with-s3-actions.html
|
||||
if s3Error := enforceBucketPolicy(bucket, "s3:ListBucket", r.URL); s3Error != ErrNone {
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
case authTypePresigned, authTypeSigned:
|
||||
if s3Error := isReqAuthenticated(r); s3Error != ErrNone {
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := api.ObjectAPI.GetBucketInfo(bucket); err != nil {
|
||||
errorIf(err, "Unable to fetch bucket info.")
|
||||
writeErrorResponse(w, r, toAPIErrorCode(err), r.URL.Path)
|
||||
return
|
||||
}
|
||||
writeSuccessResponse(w, nil)
|
||||
}
|
||||
|
||||
// DeleteBucketHandler - Delete bucket
|
||||
func (api objectAPIHandlers) DeleteBucketHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// DeleteBucket does not support bucket policies, use checkAuth to validate signature.
|
||||
if s3Error := checkAuth(r); s3Error != ErrNone {
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
bucket := vars["bucket"]
|
||||
|
||||
// Attempt to delete bucket.
|
||||
if err := api.ObjectAPI.DeleteBucket(bucket); err != nil {
|
||||
errorIf(err, "Unable to delete a bucket.")
|
||||
writeErrorResponse(w, r, toAPIErrorCode(err), r.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete bucket access policy, if present - ignore any errors.
|
||||
removeBucketPolicy(bucket, api.ObjectAPI)
|
||||
|
||||
// Delete notification config, if present - ignore any errors.
|
||||
removeNotificationConfig(bucket, api.ObjectAPI)
|
||||
|
||||
// Write success response.
|
||||
writeSuccessNoContent(w)
|
||||
}
|
||||
@@ -1,269 +0,0 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"net/http"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
const (
|
||||
bucketConfigPrefix = "buckets"
|
||||
bucketNotificationConfig = "notification.xml"
|
||||
)
|
||||
|
||||
// GetBucketNotificationHandler - This implementation of the GET
|
||||
// operation uses the notification subresource to return the
|
||||
// notification configuration of a bucket. If notifications are
|
||||
// not enabled on the bucket, the operation returns an empty
|
||||
// NotificationConfiguration element.
|
||||
func (api objectAPIHandlers) GetBucketNotificationHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Validate request authorization.
|
||||
if s3Error := checkAuth(r); s3Error != ErrNone {
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
bucket := vars["bucket"]
|
||||
// Attempt to successfully load notification config.
|
||||
nConfig, err := loadNotificationConfig(bucket, api.ObjectAPI)
|
||||
if err != nil && err != errNoSuchNotifications {
|
||||
errorIf(err, "Unable to read notification configuration.")
|
||||
writeErrorResponse(w, r, toAPIErrorCode(err), r.URL.Path)
|
||||
return
|
||||
}
|
||||
// For no notifications we write a dummy XML.
|
||||
if err == errNoSuchNotifications {
|
||||
// Complies with the s3 behavior in this regard.
|
||||
nConfig = ¬ificationConfig{}
|
||||
}
|
||||
notificationBytes, err := xml.Marshal(nConfig)
|
||||
if err != nil {
|
||||
// For any marshalling failure.
|
||||
errorIf(err, "Unable to marshal notification configuration into XML.", err)
|
||||
writeErrorResponse(w, r, toAPIErrorCode(err), r.URL.Path)
|
||||
return
|
||||
}
|
||||
// Success.
|
||||
writeSuccessResponse(w, notificationBytes)
|
||||
}
|
||||
|
||||
// PutBucketNotificationHandler - Minio notification feature enables
|
||||
// you to receive notifications when certain events happen in your bucket.
|
||||
// Using this API, you can replace an existing notification configuration.
|
||||
// The configuration is an XML file that defines the event types that you
|
||||
// want Minio to publish and the destination where you want Minio to publish
|
||||
// an event notification when it detects an event of the specified type.
|
||||
// By default, your bucket has no event notifications configured. That is,
|
||||
// the notification configuration will be an empty NotificationConfiguration.
|
||||
func (api objectAPIHandlers) PutBucketNotificationHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Validate request authorization.
|
||||
if s3Error := checkAuth(r); s3Error != ErrNone {
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
bucket := vars["bucket"]
|
||||
|
||||
_, err := api.ObjectAPI.GetBucketInfo(bucket)
|
||||
if err != nil {
|
||||
errorIf(err, "Unable to bucket info.")
|
||||
writeErrorResponse(w, r, toAPIErrorCode(err), r.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
// If Content-Length is unknown or zero, deny the request. PutBucketNotification
|
||||
// always needs a Content-Length if incoming request is not chunked.
|
||||
if !contains(r.TransferEncoding, "chunked") {
|
||||
if r.ContentLength == -1 {
|
||||
writeErrorResponse(w, r, ErrMissingContentLength, r.URL.Path)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Reads the incoming notification configuration.
|
||||
var buffer bytes.Buffer
|
||||
var bufferSize int64
|
||||
if r.ContentLength >= 0 {
|
||||
bufferSize, err = io.CopyN(&buffer, r.Body, r.ContentLength)
|
||||
} else {
|
||||
bufferSize, err = io.Copy(&buffer, r.Body)
|
||||
}
|
||||
if err != nil {
|
||||
errorIf(err, "Unable to read incoming body.")
|
||||
writeErrorResponse(w, r, toAPIErrorCode(err), r.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
var notificationCfg notificationConfig
|
||||
// Unmarshal notification bytes.
|
||||
notificationConfigBytes := buffer.Bytes()
|
||||
if err = xml.Unmarshal(notificationConfigBytes, ¬ificationCfg); err != nil {
|
||||
errorIf(err, "Unable to parse notification configuration XML.")
|
||||
writeErrorResponse(w, r, ErrMalformedXML, r.URL.Path)
|
||||
return
|
||||
} // Successfully marshalled notification configuration.
|
||||
|
||||
// Validate unmarshalled bucket notification configuration.
|
||||
if s3Error := validateNotificationConfig(notificationCfg); s3Error != ErrNone {
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
// Proceed to save notification configuration.
|
||||
notificationConfigPath := path.Join(bucketConfigPrefix, bucket, bucketNotificationConfig)
|
||||
_, err = api.ObjectAPI.PutObject(minioMetaBucket, notificationConfigPath, bufferSize, bytes.NewReader(buffer.Bytes()), nil)
|
||||
if err != nil {
|
||||
errorIf(err, "Unable to write bucket notification configuration.")
|
||||
writeErrorResponse(w, r, toAPIErrorCode(err), r.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
// Set bucket notification config.
|
||||
eventN.SetBucketNotificationConfig(bucket, ¬ificationCfg)
|
||||
|
||||
// Success.
|
||||
writeSuccessResponse(w, nil)
|
||||
}
|
||||
|
||||
// writeNotification marshals notification message before writing to client.
|
||||
func writeNotification(w http.ResponseWriter, notification map[string][]NotificationEvent) error {
|
||||
// Invalid response writer.
|
||||
if w == nil {
|
||||
return errInvalidArgument
|
||||
}
|
||||
// Invalid notification input.
|
||||
if notification == nil {
|
||||
return errInvalidArgument
|
||||
}
|
||||
// Marshal notification data into XML and write to client.
|
||||
notificationBytes, err := json.Marshal(¬ification)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Add additional CRLF characters for client to
|
||||
// differentiate the individual events properly.
|
||||
_, err = w.Write(append(notificationBytes, crlf...))
|
||||
// Make sure we have flushed, this would set Transfer-Encoding: chunked.
|
||||
w.(http.Flusher).Flush()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CRLF character used for chunked transfer in accordance with HTTP standards.
|
||||
var crlf = []byte("\r\n")
|
||||
|
||||
// sendBucketNotification - writes notification back to client on the response writer
|
||||
// for each notification input, otherwise writes whitespace characters periodically
|
||||
// to keep the connection active. Each notification messages are terminated by CRLF
|
||||
// character. Upon any error received on response writer the for loop exits.
|
||||
//
|
||||
// TODO - do not log for all errors.
|
||||
func sendBucketNotification(w http.ResponseWriter, arnListenerCh <-chan []NotificationEvent) {
|
||||
var dummyEvents = map[string][]NotificationEvent{"Records": nil}
|
||||
// Continuously write to client either timely empty structures
|
||||
// every 5 seconds, or return back the notifications.
|
||||
for {
|
||||
select {
|
||||
case events := <-arnListenerCh:
|
||||
if err := writeNotification(w, map[string][]NotificationEvent{"Records": events}); err != nil {
|
||||
errorIf(err, "Unable to write notification to client.")
|
||||
return
|
||||
}
|
||||
case <-time.After(5 * time.Second):
|
||||
if err := writeNotification(w, dummyEvents); err != nil {
|
||||
errorIf(err, "Unable to write notification to client.")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ListenBucketNotificationHandler - list bucket notifications.
|
||||
func (api objectAPIHandlers) ListenBucketNotificationHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Validate request authorization.
|
||||
if s3Error := checkAuth(r); s3Error != ErrNone {
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
bucket := vars["bucket"]
|
||||
|
||||
// Get notification ARN.
|
||||
topicARN := r.URL.Query().Get("notificationARN")
|
||||
if topicARN == "" {
|
||||
writeErrorResponse(w, r, ErrARNNotification, r.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate if bucket exists.
|
||||
_, err := api.ObjectAPI.GetBucketInfo(bucket)
|
||||
if err != nil {
|
||||
errorIf(err, "Unable to bucket info.")
|
||||
writeErrorResponse(w, r, toAPIErrorCode(err), r.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
notificationCfg := eventN.GetBucketNotificationConfig(bucket)
|
||||
if notificationCfg == nil {
|
||||
writeErrorResponse(w, r, ErrARNNotification, r.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
// Set SNS notifications only if special "listen" sns is set in bucket
|
||||
// notification configs.
|
||||
if !isMinioSNSConfigured(topicARN, notificationCfg.TopicConfigs) {
|
||||
writeErrorResponse(w, r, ErrARNNotification, r.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
// Add all common headers.
|
||||
setCommonHeaders(w)
|
||||
|
||||
// Create a new notification event channel.
|
||||
nEventCh := make(chan []NotificationEvent)
|
||||
// Close the listener channel.
|
||||
defer close(nEventCh)
|
||||
|
||||
// Set sns target.
|
||||
eventN.SetSNSTarget(topicARN, nEventCh)
|
||||
// Remove sns listener after the writer has closed or the client disconnected.
|
||||
defer eventN.RemoveSNSTarget(topicARN, nEventCh)
|
||||
|
||||
// Start sending bucket notifications.
|
||||
sendBucketNotification(w, nEventCh)
|
||||
}
|
||||
|
||||
// Removes notification.xml for a given bucket, only used during DeleteBucket.
|
||||
func removeNotificationConfig(bucket string, objAPI ObjectLayer) error {
|
||||
// Verify bucket is valid.
|
||||
if !IsValidBucketName(bucket) {
|
||||
return BucketNameInvalid{Bucket: bucket}
|
||||
}
|
||||
|
||||
notificationConfigPath := path.Join(bucketConfigPrefix, bucket, bucketNotificationConfig)
|
||||
return objAPI.DeleteObject(minioMetaBucket, notificationConfigPath)
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Implement a dummy flush writer.
|
||||
type flushWriter struct {
|
||||
io.Writer
|
||||
}
|
||||
|
||||
// Flush writer is a dummy writer compatible with http.Flusher and http.ResponseWriter.
|
||||
func (f *flushWriter) Flush() {}
|
||||
func (f *flushWriter) Write(b []byte) (n int, err error) { return f.Writer.Write(b) }
|
||||
func (f *flushWriter) Header() http.Header { return http.Header{} }
|
||||
func (f *flushWriter) WriteHeader(code int) {}
|
||||
|
||||
func newFlushWriter(writer io.Writer) *flushWriter {
|
||||
return &flushWriter{writer}
|
||||
}
|
||||
|
||||
// Tests write notification code.
|
||||
func TestWriteNotification(t *testing.T) {
|
||||
// Initialize a new test config.
|
||||
root, err := newTestConfig("us-east-1")
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to initialize test config %s", err)
|
||||
}
|
||||
defer removeAll(root)
|
||||
|
||||
var buffer bytes.Buffer
|
||||
// Collection of test cases for each event writer.
|
||||
testCases := []struct {
|
||||
writer *flushWriter
|
||||
event map[string][]NotificationEvent
|
||||
err error
|
||||
}{
|
||||
// Invalid input argument with writer `nil` - Test - 1
|
||||
{
|
||||
writer: nil,
|
||||
event: nil,
|
||||
err: errInvalidArgument,
|
||||
},
|
||||
// Invalid input argument with event `nil` - Test - 2
|
||||
{
|
||||
writer: newFlushWriter(ioutil.Discard),
|
||||
event: nil,
|
||||
err: errInvalidArgument,
|
||||
},
|
||||
// Unmarshal and write, validate last 5 bytes. - Test - 3
|
||||
{
|
||||
writer: newFlushWriter(&buffer),
|
||||
event: map[string][]NotificationEvent{
|
||||
"Records": {newNotificationEvent(eventData{
|
||||
Type: ObjectCreatedPut,
|
||||
Bucket: "testbucket",
|
||||
ObjInfo: ObjectInfo{
|
||||
Name: "key",
|
||||
},
|
||||
ReqParams: map[string]string{
|
||||
"ip": "10.1.10.1",
|
||||
}}),
|
||||
},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
}
|
||||
// Validates all the testcases for writing notification.
|
||||
for _, testCase := range testCases {
|
||||
err := writeNotification(testCase.writer, testCase.event)
|
||||
if err != testCase.err {
|
||||
t.Errorf("Unable to write notification %s", err)
|
||||
}
|
||||
// Validates if the ending string has 'crlf'
|
||||
if err == nil && !bytes.HasSuffix(buffer.Bytes(), crlf) {
|
||||
buf := buffer.Bytes()[buffer.Len()-5 : 0]
|
||||
t.Errorf("Invalid suffix found from the writer last 5 bytes %s, expected `\r\n`", string(buf))
|
||||
}
|
||||
// Not printing 'buf' on purpose, validates look for string '10.1.10.1'.
|
||||
if err == nil && !bytes.Contains(buffer.Bytes(), []byte("10.1.10.1")) {
|
||||
// Enable when debugging)
|
||||
// fmt.Println(string(buffer.Bytes()))
|
||||
t.Errorf("Requested content couldn't be found, expected `10.1.10.1`")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,288 +0,0 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
mux "github.com/gorilla/mux"
|
||||
"github.com/minio/minio/pkg/wildcard"
|
||||
)
|
||||
|
||||
// maximum supported access policy size.
|
||||
const maxAccessPolicySize = 20 * 1024 // 20KiB.
|
||||
|
||||
// Verify if a given action is valid for the url path based on the
|
||||
// existing bucket access policy.
|
||||
func bucketPolicyEvalStatements(action string, resource string, conditions map[string]string, statements []policyStatement) bool {
|
||||
for _, statement := range statements {
|
||||
if bucketPolicyMatchStatement(action, resource, conditions, statement) {
|
||||
if statement.Effect == "Allow" {
|
||||
return true
|
||||
}
|
||||
// Do not uncomment kept here for readability.
|
||||
// else statement.Effect == "Deny"
|
||||
return false
|
||||
}
|
||||
}
|
||||
// None match so deny.
|
||||
return false
|
||||
}
|
||||
|
||||
// Verify if action, resource and conditions match input policy statement.
|
||||
func bucketPolicyMatchStatement(action string, resource string, conditions map[string]string, statement policyStatement) bool {
|
||||
// Verify if action matches.
|
||||
if bucketPolicyActionMatch(action, statement) {
|
||||
// Verify if resource matches.
|
||||
if bucketPolicyResourceMatch(resource, statement) {
|
||||
// Verify if condition matches.
|
||||
if bucketPolicyConditionMatch(conditions, statement) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Verify if given action matches with policy statement.
|
||||
func bucketPolicyActionMatch(action string, statement policyStatement) bool {
|
||||
for _, policyAction := range statement.Actions {
|
||||
if matched := actionMatch(policyAction, action); matched {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Match function matches wild cards in 'pattern' for resource.
|
||||
func resourceMatch(pattern, resource string) bool {
|
||||
return wildcard.MatchExtended(pattern, resource)
|
||||
}
|
||||
|
||||
// Match function matches wild cards in 'pattern' for action.
|
||||
func actionMatch(pattern, action string) bool {
|
||||
return wildcard.Match(pattern, action)
|
||||
}
|
||||
|
||||
// Verify if given resource matches with policy statement.
|
||||
func bucketPolicyResourceMatch(resource string, statement policyStatement) bool {
|
||||
for _, resourcep := range statement.Resources {
|
||||
// the resource rule for object could contain "*" wild card.
|
||||
// the requested object can be given access based on the already set bucket policy if
|
||||
// the match is successful.
|
||||
// More info: http://docs.aws.amazon.com/AmazonS3/latest/dev/s3-arn-format.html .
|
||||
if matched := resourceMatch(resourcep, resource); matched {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Verify if given condition matches with policy statement.
|
||||
func bucketPolicyConditionMatch(conditions map[string]string, statement policyStatement) bool {
|
||||
// Supports following conditions.
|
||||
// - StringEquals
|
||||
// - StringNotEquals
|
||||
//
|
||||
// Supported applicable condition keys for each conditions.
|
||||
// - s3:prefix
|
||||
// - s3:max-keys
|
||||
var conditionMatches = true
|
||||
for condition, conditionKeys := range statement.Conditions {
|
||||
if condition == "StringEquals" {
|
||||
if conditionKeys["s3:prefix"] != conditions["prefix"] {
|
||||
conditionMatches = false
|
||||
break
|
||||
}
|
||||
if conditionKeys["s3:max-keys"] != conditions["max-keys"] {
|
||||
conditionMatches = false
|
||||
break
|
||||
}
|
||||
} else if condition == "StringNotEquals" {
|
||||
if conditionKeys["s3:prefix"] == conditions["prefix"] {
|
||||
conditionMatches = false
|
||||
break
|
||||
}
|
||||
if conditionKeys["s3:max-keys"] == conditions["max-keys"] {
|
||||
conditionMatches = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return conditionMatches
|
||||
}
|
||||
|
||||
// PutBucketPolicyHandler - PUT Bucket policy
|
||||
// -----------------
|
||||
// This implementation of the PUT operation uses the policy
|
||||
// subresource to add to or replace a policy on a bucket
|
||||
func (api objectAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
bucket := vars["bucket"]
|
||||
switch getRequestAuthType(r) {
|
||||
default:
|
||||
// For all unknown auth types return error.
|
||||
writeErrorResponse(w, r, ErrAccessDenied, r.URL.Path)
|
||||
return
|
||||
case authTypePresigned, authTypeSigned:
|
||||
if s3Error := isReqAuthenticated(r); s3Error != ErrNone {
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// If Content-Length is unknown or zero, deny the
|
||||
// request. PutBucketPolicy always needs a Content-Length if
|
||||
// incoming request is not chunked.
|
||||
if !contains(r.TransferEncoding, "chunked") {
|
||||
if r.ContentLength == -1 || r.ContentLength == 0 {
|
||||
writeErrorResponse(w, r, ErrMissingContentLength, r.URL.Path)
|
||||
return
|
||||
}
|
||||
// If Content-Length is greater than maximum allowed policy size.
|
||||
if r.ContentLength > maxAccessPolicySize {
|
||||
writeErrorResponse(w, r, ErrEntityTooLarge, r.URL.Path)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Read access policy up to maxAccessPolicySize.
|
||||
// http://docs.aws.amazon.com/AmazonS3/latest/dev/access-policy-language-overview.html
|
||||
// bucket policies are limited to 20KB in size, using a limit reader.
|
||||
policyBytes, err := ioutil.ReadAll(io.LimitReader(r.Body, maxAccessPolicySize))
|
||||
if err != nil {
|
||||
errorIf(err, "Unable to read from client.")
|
||||
writeErrorResponse(w, r, toAPIErrorCode(err), r.URL.Path)
|
||||
return
|
||||
}
|
||||
// Parse bucket policy.
|
||||
var policy = &bucketPolicy{}
|
||||
err = parseBucketPolicy(bytes.NewReader(policyBytes), policy)
|
||||
if err != nil {
|
||||
errorIf(err, "Unable to parse bucket policy.")
|
||||
writeErrorResponse(w, r, ErrInvalidPolicyDocument, r.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
// Parse check bucket policy.
|
||||
if s3Error := checkBucketPolicyResources(bucket, policy); s3Error != ErrNone {
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
// Save bucket policy.
|
||||
if err = writeBucketPolicy(bucket, api.ObjectAPI, bytes.NewReader(policyBytes), int64(len(policyBytes))); err != nil {
|
||||
errorIf(err, "Unable to write bucket policy.")
|
||||
switch err.(type) {
|
||||
case BucketNameInvalid:
|
||||
writeErrorResponse(w, r, ErrInvalidBucketName, r.URL.Path)
|
||||
default:
|
||||
writeErrorResponse(w, r, ErrInternalError, r.URL.Path)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Set the bucket policy in memory.
|
||||
globalBucketPolicies.SetBucketPolicy(bucket, policy)
|
||||
|
||||
// Success.
|
||||
writeSuccessNoContent(w)
|
||||
}
|
||||
|
||||
// DeleteBucketPolicyHandler - DELETE Bucket policy
|
||||
// -----------------
|
||||
// This implementation of the DELETE operation uses the policy
|
||||
// subresource to add to remove a policy on a bucket.
|
||||
func (api objectAPIHandlers) DeleteBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
bucket := vars["bucket"]
|
||||
|
||||
switch getRequestAuthType(r) {
|
||||
default:
|
||||
// For all unknown auth types return error.
|
||||
writeErrorResponse(w, r, ErrAccessDenied, r.URL.Path)
|
||||
return
|
||||
case authTypePresigned, authTypeSigned:
|
||||
if s3Error := isReqAuthenticated(r); s3Error != ErrNone {
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Delete bucket access policy.
|
||||
if err := removeBucketPolicy(bucket, api.ObjectAPI); err != nil {
|
||||
errorIf(err, "Unable to remove bucket policy.")
|
||||
switch err.(type) {
|
||||
case BucketNameInvalid:
|
||||
writeErrorResponse(w, r, ErrInvalidBucketName, r.URL.Path)
|
||||
case BucketPolicyNotFound:
|
||||
writeErrorResponse(w, r, ErrNoSuchBucketPolicy, r.URL.Path)
|
||||
default:
|
||||
writeErrorResponse(w, r, ErrInternalError, r.URL.Path)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Remove bucket policy.
|
||||
globalBucketPolicies.RemoveBucketPolicy(bucket)
|
||||
|
||||
// Success.
|
||||
writeSuccessNoContent(w)
|
||||
}
|
||||
|
||||
// GetBucketPolicyHandler - GET Bucket policy
|
||||
// -----------------
|
||||
// This operation uses the policy
|
||||
// subresource to return the policy of a specified bucket.
|
||||
func (api objectAPIHandlers) GetBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
bucket := vars["bucket"]
|
||||
|
||||
switch getRequestAuthType(r) {
|
||||
default:
|
||||
// For all unknown auth types return error.
|
||||
writeErrorResponse(w, r, ErrAccessDenied, r.URL.Path)
|
||||
return
|
||||
case authTypePresigned, authTypeSigned:
|
||||
if s3Error := isReqAuthenticated(r); s3Error != ErrNone {
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Read bucket access policy.
|
||||
policy, err := readBucketPolicy(bucket, api.ObjectAPI)
|
||||
if err != nil {
|
||||
errorIf(err, "Unable to read bucket policy.")
|
||||
switch err.(type) {
|
||||
case BucketNameInvalid:
|
||||
writeErrorResponse(w, r, ErrInvalidBucketName, r.URL.Path)
|
||||
case BucketPolicyNotFound:
|
||||
writeErrorResponse(w, r, ErrNoSuchBucketPolicy, r.URL.Path)
|
||||
default:
|
||||
writeErrorResponse(w, r, ErrInternalError, r.URL.Path)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Write to client.
|
||||
fmt.Fprint(w, policy)
|
||||
}
|
||||
@@ -1,547 +0,0 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Tests validate Bucket policy resource matcher.
|
||||
func TestBucketPolicyResourceMatch(t *testing.T) {
|
||||
|
||||
// generates statement with given resource..
|
||||
generateStatement := func(resource string) policyStatement {
|
||||
statement := policyStatement{}
|
||||
statement.Resources = []string{resource}
|
||||
return statement
|
||||
}
|
||||
|
||||
// generates resource prefix.
|
||||
generateResource := func(bucketName, objectName string) string {
|
||||
return AWSResourcePrefix + bucketName + "/" + objectName
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
resourceToMatch string
|
||||
statement policyStatement
|
||||
expectedResourceMatch bool
|
||||
}{
|
||||
// Test case 1-4.
|
||||
// Policy with resource ending with bucket/* allows access to all objects inside the given bucket.
|
||||
{generateResource("minio-bucket", ""), generateStatement(fmt.Sprintf("%s%s", AWSResourcePrefix, "minio-bucket"+"/*")), true},
|
||||
{generateResource("minio-bucket", ""), generateStatement(fmt.Sprintf("%s%s", AWSResourcePrefix, "minio-bucket"+"/*")), true},
|
||||
{generateResource("minio-bucket", ""), generateStatement(fmt.Sprintf("%s%s", AWSResourcePrefix, "minio-bucket"+"/*")), true},
|
||||
{generateResource("minio-bucket", ""), generateStatement(fmt.Sprintf("%s%s", AWSResourcePrefix, "minio-bucket"+"/*")), true},
|
||||
// Test case - 5.
|
||||
// Policy with resource ending with bucket/oo* should not allow access to bucket/output.txt.
|
||||
{generateResource("minio-bucket", "output.txt"), generateStatement(fmt.Sprintf("%s%s", AWSResourcePrefix, "minio-bucket"+"/oo*")), false},
|
||||
// Test case - 6.
|
||||
// Policy with resource ending with bucket/oo* should allow access to bucket/ootput.txt.
|
||||
{generateResource("minio-bucket", "ootput.txt"), generateStatement(fmt.Sprintf("%s%s", AWSResourcePrefix, "minio-bucket"+"/oo*")), true},
|
||||
// Test case - 7.
|
||||
// Policy with resource ending with bucket/oo* allows access to all subfolders starting with "oo" inside given bucket.
|
||||
{generateResource("minio-bucket", "oop-bucket/my-file"), generateStatement(fmt.Sprintf("%s%s", AWSResourcePrefix, "minio-bucket"+"/oo*")), true},
|
||||
// Test case - 8.
|
||||
{generateResource("minio-bucket", "Asia/India/1.pjg"), generateStatement(fmt.Sprintf("%s%s", AWSResourcePrefix, "minio-bucket"+"/Asia/Japan/*")), false},
|
||||
// Test case - 9.
|
||||
{generateResource("minio-bucket", "Asia/India/1.pjg"), generateStatement(fmt.Sprintf("%s%s", AWSResourcePrefix, "minio-bucket"+"/Asia/Japan/*")), false},
|
||||
// Test case - 10.
|
||||
// Proves that the name space is flat.
|
||||
{generateResource("minio-bucket", "Africa/Bihar/India/design_info.doc/Bihar"), generateStatement(fmt.Sprintf("%s%s", AWSResourcePrefix,
|
||||
"minio-bucket"+"/*/India/*/Bihar")), true},
|
||||
// Test case - 11.
|
||||
// Proves that the name space is flat.
|
||||
{generateResource("minio-bucket", "Asia/China/India/States/Bihar/output.txt"), generateStatement(fmt.Sprintf("%s%s", AWSResourcePrefix,
|
||||
"minio-bucket"+"/*/India/*/Bihar/*")), true},
|
||||
}
|
||||
for i, testCase := range testCases {
|
||||
actualResourceMatch := bucketPolicyResourceMatch(testCase.resourceToMatch, testCase.statement)
|
||||
if testCase.expectedResourceMatch != actualResourceMatch {
|
||||
t.Errorf("Test %d: Expected Resource match to be `%v`, but instead found it to be `%v`", i+1, testCase.expectedResourceMatch, actualResourceMatch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestBucketPolicyActionMatch - Test validates whether given action on the
|
||||
// bucket/object matches the allowed actions in policyStatement.
|
||||
// This test preserves the allowed actions for all 3 sets of policies, that is read-write,read-only, write-only.
|
||||
// The intention of the test is to catch any changes made to allowed action for on eof the above 3 major policy groups mentioned.
|
||||
func TestBucketPolicyActionMatch(t *testing.T) {
|
||||
bucketName := getRandomBucketName()
|
||||
objectPrefix := "test-object"
|
||||
|
||||
testCases := []struct {
|
||||
action string
|
||||
statement policyStatement
|
||||
expectedResult bool
|
||||
}{
|
||||
// s3:GetBucketLocation is the action necessary to be present in the bucket policy to allow
|
||||
// fetching of bucket location on an Anonymous/unsigned request.
|
||||
|
||||
//r ead-write bucket policy is expected to allow GetBucketLocation operation on an anonymous request (Test case - 1).
|
||||
{"s3:GetBucketLocation", getReadWriteBucketStatement(bucketName, objectPrefix), true},
|
||||
// write-only bucket policy is expected to allow GetBucketLocation operation on an anonymous request (Test case - 2).
|
||||
{"s3:GetBucketLocation", getWriteOnlyBucketStatement(bucketName, objectPrefix), true},
|
||||
// read-only bucket policy is expected to allow GetBucketLocation operation on an anonymous request (Test case - 3).
|
||||
{"s3:GetBucketLocation", getReadOnlyBucketStatement(bucketName, objectPrefix), true},
|
||||
|
||||
// Any of the Object level access permissions shouldn't allow for GetBucketLocation operation on an Anonymous/unsigned request (Test cases 4-6).
|
||||
{"s3:GetBucketLocation", getReadWriteObjectStatement(bucketName, objectPrefix), false},
|
||||
{"s3:GetBucketLocation", getWriteOnlyObjectStatement(bucketName, objectPrefix), false},
|
||||
{"s3:GetBucketLocation", getReadOnlyObjectStatement(bucketName, objectPrefix), false},
|
||||
|
||||
// s3:ListBucketMultipartUploads is the action necessary to be present in the bucket policy to allow
|
||||
// Listing of multipart uploads in a given bucket for an Anonymous/unsigned request.
|
||||
|
||||
//read-write bucket policy is expected to allow ListBucketMultipartUploads operation on an anonymous request (Test case 7).
|
||||
{"s3:ListBucketMultipartUploads", getReadWriteBucketStatement(bucketName, objectPrefix), true},
|
||||
// write-only bucket policy is expected to allow ListBucketMultipartUploads operation on an anonymous request (Test case 8).
|
||||
{"s3:ListBucketMultipartUploads", getWriteOnlyBucketStatement(bucketName, objectPrefix), true},
|
||||
// read-only bucket policy is expected to not allow ListBucketMultipartUploads operation on an anonymous request (Test case 9).
|
||||
// the allowed actions in read-only bucket statement are "s3:GetBucketLocation","s3:ListBucket",
|
||||
// this should not allow for ListBucketMultipartUploads operations.
|
||||
{"s3:ListBucketMultipartUploads", getReadOnlyBucketStatement(bucketName, objectPrefix), false},
|
||||
|
||||
// Any of the object level policy will not allow for s3:ListBucketMultipartUploads (Test cases 10-12).
|
||||
{"s3:ListBucketMultipartUploads", getReadWriteObjectStatement(bucketName, objectPrefix), false},
|
||||
{"s3:ListBucketMultipartUploads", getWriteOnlyObjectStatement(bucketName, objectPrefix), false},
|
||||
{"s3:ListBucketMultipartUploads", getReadOnlyObjectStatement(bucketName, objectPrefix), false},
|
||||
|
||||
// s3:ListBucket is the action necessary to be present in the bucket policy to allow
|
||||
// listing of all objects inside a given bucket on an Anonymous/unsigned request.
|
||||
|
||||
// Cases for testing ListBucket access for different Bucket level access permissions.
|
||||
// read-only bucket policy is expected to allow ListBucket operation on an anonymous request (Test case 13).
|
||||
{"s3:ListBucket", getReadOnlyBucketStatement(bucketName, objectPrefix), true},
|
||||
// read-write bucket policy is expected to allow ListBucket operation on an anonymous request (Test case 14).
|
||||
{"s3:ListBucket", getReadWriteBucketStatement(bucketName, objectPrefix), true},
|
||||
// write-only bucket policy is expected to not allow ListBucket operation on an anonymous request (Test case 15).
|
||||
// the allowed actions in write-only bucket statement are "s3:GetBucketLocation", "s3:ListBucketMultipartUploads",
|
||||
// this should not allow for ListBucket operations.
|
||||
{"s3:ListBucket", getWriteOnlyBucketStatement(bucketName, objectPrefix), false},
|
||||
|
||||
// Cases for testing ListBucket access for different Object level access permissions (Test cases 16-18).
|
||||
// Any of the Object level access permissions shouldn't allow for ListBucket operation on an Anonymous/unsigned request.
|
||||
{"s3:ListBucket", getReadOnlyObjectStatement(bucketName, objectPrefix), false},
|
||||
{"s3:ListBucket", getReadWriteObjectStatement(bucketName, objectPrefix), false},
|
||||
{"s3:ListBucket", getWriteOnlyObjectStatement(bucketName, objectPrefix), false},
|
||||
|
||||
// s3:DeleteObject is the action necessary to be present in the bucket policy to allow
|
||||
// deleting/removal of objects inside a given bucket for an Anonymous/unsigned request.
|
||||
|
||||
// Cases for testing DeleteObject access for different Bucket level access permissions (Test cases 19-21).
|
||||
// Any of the Bucket level access permissions shouldn't allow for DeleteObject operation on an Anonymous/unsigned request.
|
||||
{"s3:DeleteObject", getReadOnlyBucketStatement(bucketName, objectPrefix), false},
|
||||
{"s3:DeleteObject", getReadWriteBucketStatement(bucketName, objectPrefix), false},
|
||||
{"s3:DeleteObject", getWriteOnlyBucketStatement(bucketName, objectPrefix), false},
|
||||
|
||||
// Cases for testing DeleteObject access for different Object level access permissions (Test cases 22).
|
||||
// read-only bucket policy is expected to not allow Delete Object operation on an anonymous request.
|
||||
{"s3:DeleteObject", getReadOnlyObjectStatement(bucketName, objectPrefix), false},
|
||||
// read-write bucket policy is expected to allow Delete Bucket operation on an anonymous request (Test cases 23).
|
||||
{"s3:DeleteObject", getReadWriteObjectStatement(bucketName, objectPrefix), true},
|
||||
// write-only bucket policy is expected to allow Delete Object operation on an anonymous request (Test cases 24).
|
||||
{"s3:DeleteObject", getWriteOnlyObjectStatement(bucketName, objectPrefix), true},
|
||||
|
||||
// s3:AbortMultipartUpload is the action necessary to be present in the bucket policy to allow
|
||||
// cancelling or abortion of an already initiated multipart upload operation for an Anonymous/unsigned request.
|
||||
|
||||
// Cases for testing AbortMultipartUpload access for different Bucket level access permissions (Test cases 25-27).
|
||||
// Any of the Bucket level access permissions shouldn't allow for AbortMultipartUpload operation on an Anonymous/unsigned request.
|
||||
{"s3:AbortMultipartUpload", getReadOnlyBucketStatement(bucketName, objectPrefix), false},
|
||||
{"s3:AbortMultipartUpload", getReadWriteBucketStatement(bucketName, objectPrefix), false},
|
||||
{"s3:AbortMultipartUpload", getWriteOnlyBucketStatement(bucketName, objectPrefix), false},
|
||||
|
||||
// Cases for testing AbortMultipartUpload access for different Object level access permissions.
|
||||
// read-only object policy is expected to not allow AbortMultipartUpload operation on an anonymous request (Test case 28).
|
||||
{"s3:AbortMultipartUpload", getReadOnlyObjectStatement(bucketName, objectPrefix), false},
|
||||
// read-write object policy is expected to allow AbortMultipartUpload operation on an anonymous request (Test case 29).
|
||||
{"s3:AbortMultipartUpload", getReadWriteObjectStatement(bucketName, objectPrefix), true},
|
||||
// write-only object policy is expected to allow AbortMultipartUpload operation on an anonymous request (Test case 30).
|
||||
{"s3:AbortMultipartUpload", getWriteOnlyObjectStatement(bucketName, objectPrefix), true},
|
||||
|
||||
// s3:PutObject is the action necessary to be present in the bucket policy to allow
|
||||
// uploading of an object for an Anonymous/unsigned request.
|
||||
|
||||
// Cases for testing PutObject access for different Bucket level access permissions (Test cases 31-33).
|
||||
// Any of the Bucket level access permissions shouldn't allow for PutObject operation on an Anonymous/unsigned request.
|
||||
{"s3:PutObject", getReadOnlyBucketStatement(bucketName, objectPrefix), false},
|
||||
{"s3:PutObject", getReadWriteBucketStatement(bucketName, objectPrefix), false},
|
||||
{"s3:PutObject", getWriteOnlyBucketStatement(bucketName, objectPrefix), false},
|
||||
|
||||
// Cases for testing PutObject access for different Object level access permissions.
|
||||
// read-only object policy is expected to not allow PutObject operation on an anonymous request (Test case 34).
|
||||
{"s3:PutObject", getReadOnlyObjectStatement(bucketName, objectPrefix), false},
|
||||
// read-write object policy is expected to allow PutObject operation on an anonymous request (Test case 35).
|
||||
{"s3:PutObject", getReadWriteObjectStatement(bucketName, objectPrefix), true},
|
||||
// write-only object policy is expected to allow PutObject operation on an anonymous request (Test case 36).
|
||||
{"s3:PutObject", getWriteOnlyObjectStatement(bucketName, objectPrefix), true},
|
||||
|
||||
// s3:GetObject is the action necessary to be present in the bucket policy to allow
|
||||
// downloading of an object for an Anonymous/unsigned request.
|
||||
|
||||
// Cases for testing GetObject access for different Bucket level access permissions (Test cases 37-39).
|
||||
// Any of the Bucket level access permissions shouldn't allow for GetObject operation on an Anonymous/unsigned request.
|
||||
{"s3:GetObject", getReadOnlyBucketStatement(bucketName, objectPrefix), false},
|
||||
{"s3:GetObject", getReadWriteBucketStatement(bucketName, objectPrefix), false},
|
||||
{"s3:GetObject", getWriteOnlyBucketStatement(bucketName, objectPrefix), false},
|
||||
|
||||
// Cases for testing GetObject access for different Object level access permissions.
|
||||
// read-only bucket policy is expected to allow downloading of an Object on an anonymous request (Test case 40).
|
||||
{"s3:GetObject", getReadOnlyObjectStatement(bucketName, objectPrefix), true},
|
||||
// read-write bucket policy is expected to allow downloading of an Object on an anonymous request (Test case 41).
|
||||
{"s3:GetObject", getReadWriteObjectStatement(bucketName, objectPrefix), true},
|
||||
// write-only bucket policy is expected to not allow downloading of an Object on an anonymous request (Test case 42).
|
||||
{"s3:GetObject", getWriteOnlyObjectStatement(bucketName, objectPrefix), false},
|
||||
|
||||
// s3:ListMultipartUploadParts is the action necessary to be present in the bucket policy to allow
|
||||
// Listing of uploaded parts for an Anonymous/unsigned request.
|
||||
|
||||
// Any of the Bucket level access permissions shouldn't allow for ListMultipartUploadParts operation on an Anonymous/unsigned request.
|
||||
// read-only bucket policy is expected to not allow ListMultipartUploadParts operation on an anonymous request (Test cases 43-45).
|
||||
{"s3:ListMultipartUploadParts", getReadOnlyBucketStatement(bucketName, objectPrefix), false},
|
||||
{"s3:ListMultipartUploadParts", getReadWriteBucketStatement(bucketName, objectPrefix), false},
|
||||
{"s3:ListMultipartUploadParts", getWriteOnlyBucketStatement(bucketName, objectPrefix), false},
|
||||
|
||||
// read-only object policy is expected to not allow ListMultipartUploadParts operation on an anonymous request (Test case 46).
|
||||
{"s3:ListMultipartUploadParts", getReadOnlyObjectStatement(bucketName, objectPrefix), false},
|
||||
// read-write object policy is expected to allow ListMultipartUploadParts operation on an anonymous request (Test case 47).
|
||||
{"s3:ListMultipartUploadParts", getReadWriteObjectStatement(bucketName, objectPrefix), true},
|
||||
// write-only object policy is expected to allow ListMultipartUploadParts operation on an anonymous request (Test case 48).
|
||||
{"s3:ListMultipartUploadParts", getWriteOnlyObjectStatement(bucketName, objectPrefix), true},
|
||||
}
|
||||
for i, testCase := range testCases {
|
||||
actualResult := bucketPolicyActionMatch(testCase.action, testCase.statement)
|
||||
if testCase.expectedResult != actualResult {
|
||||
t.Errorf("Test %d: Expected the result to be `%v`, but instead found it to be `%v`", i+1, testCase.expectedResult, actualResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapper for calling Put Bucket Policy HTTP handler tests for both XL multiple disks and single node setup.
|
||||
func TestPutBucketPolicyHandler(t *testing.T) {
|
||||
ExecObjectLayerTest(t, testPutBucketPolicyHandler)
|
||||
}
|
||||
|
||||
// testPutBucketPolicyHandler - Test for Bucket policy end point.
|
||||
// TODO: Add exhaustive cases with various combination of statement fields.
|
||||
func testPutBucketPolicyHandler(obj ObjectLayer, instanceType string, t TestErrHandler) {
|
||||
initBucketPolicies(obj)
|
||||
|
||||
// get random bucket name.
|
||||
bucketName := getRandomBucketName()
|
||||
// Create bucket.
|
||||
err := obj.MakeBucket(bucketName)
|
||||
if err != nil {
|
||||
// failed to create newbucket, abort.
|
||||
t.Fatalf("%s : %s", instanceType, err)
|
||||
}
|
||||
// Register the API end points with XL/FS object layer.
|
||||
apiRouter := initTestAPIEndPoints(obj, []string{"PutBucketPolicy"})
|
||||
// initialize the server and obtain the credentials and root.
|
||||
// credentials are necessary to sign the HTTP request.
|
||||
rootPath, err := newTestConfig("us-east-1")
|
||||
if err != nil {
|
||||
t.Fatalf("Init Test config failed")
|
||||
}
|
||||
// remove the root folder after the test ends.
|
||||
defer removeAll(rootPath)
|
||||
|
||||
credentials := serverConfig.GetCredential()
|
||||
|
||||
// template for constructing HTTP request body for PUT bucket policy.
|
||||
bucketPolicyTemplate := `{"Version":"2012-10-17","Statement":[{"Sid":"","Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:GetBucketLocation","s3:ListBucket"],"Resource":["arn:aws:s3:::%s"]},{"Sid":"","Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:GetObject"],"Resource":["arn:aws:s3:::%s/this*"]}]}`
|
||||
|
||||
// test cases with sample input and expected output.
|
||||
testCases := []struct {
|
||||
bucketName string
|
||||
accessKey string
|
||||
secretKey string
|
||||
// expected Response.
|
||||
expectedRespStatus int
|
||||
}{
|
||||
{bucketName, credentials.AccessKeyID, credentials.SecretAccessKey, http.StatusNoContent},
|
||||
}
|
||||
|
||||
// Iterating over the test cases, calling the function under test and asserting the response.
|
||||
for i, testCase := range testCases {
|
||||
// obtain the put bucket policy request body.
|
||||
bucketPolicyStr := fmt.Sprintf(bucketPolicyTemplate, testCase.bucketName, testCase.bucketName)
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
rec := httptest.NewRecorder()
|
||||
// construct HTTP request for PUT bucket policy endpoint.
|
||||
req, err := newTestSignedRequest("PUT", getPutPolicyURL("", testCase.bucketName),
|
||||
int64(len(bucketPolicyStr)), bytes.NewReader([]byte(bucketPolicyStr)), testCase.accessKey, testCase.secretKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, err)
|
||||
}
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler.
|
||||
// Call the ServeHTTP to execute the handler.
|
||||
apiRouter.ServeHTTP(rec, req)
|
||||
if rec.Code != testCase.expectedRespStatus {
|
||||
t.Errorf("Test %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, rec.Code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapper for calling Get Bucket Policy HTTP handler tests for both XL multiple disks and single node setup.
|
||||
func TestGetBucketPolicyHandler(t *testing.T) {
|
||||
ExecObjectLayerTest(t, testGetBucketPolicyHandler)
|
||||
}
|
||||
|
||||
// testGetBucketPolicyHandler - Test for end point which fetches the access policy json of the given bucket.
|
||||
// TODO: Add exhaustive cases with various combination of statement fields.
|
||||
func testGetBucketPolicyHandler(obj ObjectLayer, instanceType string, t TestErrHandler) {
|
||||
initBucketPolicies(obj)
|
||||
|
||||
// get random bucket name.
|
||||
bucketName := getRandomBucketName()
|
||||
// Create bucket.
|
||||
err := obj.MakeBucket(bucketName)
|
||||
if err != nil {
|
||||
// failed to create newbucket, abort.
|
||||
t.Fatalf("%s : %s", instanceType, err)
|
||||
}
|
||||
// Register the API end points with XL/FS object layer.
|
||||
// Registering only the PutBucketPolicy and GetBucketPolicy handlers.
|
||||
apiRouter := initTestAPIEndPoints(obj, []string{"PutBucketPolicy", "GetBucketPolicy"})
|
||||
// initialize the server and obtain the credentials and root.
|
||||
// credentials are necessary to sign the HTTP request.
|
||||
rootPath, err := newTestConfig("us-east-1")
|
||||
if err != nil {
|
||||
t.Fatalf("Init Test config failed")
|
||||
}
|
||||
// remove the root folder after the test ends.
|
||||
defer removeAll(rootPath)
|
||||
|
||||
credentials := serverConfig.GetCredential()
|
||||
|
||||
// template for constructing HTTP request body for PUT bucket policy.
|
||||
bucketPolicyTemplate := `{"Version":"2012-10-17","Statement":[{"Sid":"","Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:GetBucketLocation","s3:ListBucket"],"Resource":["arn:aws:s3:::%s"]},{"Sid":"","Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:GetObject"],"Resource":["arn:aws:s3:::%s/this*"]}]}`
|
||||
|
||||
// Writing bucket policy before running test on GetBucketPolicy.
|
||||
putTestPolicies := []struct {
|
||||
bucketName string
|
||||
accessKey string
|
||||
secretKey string
|
||||
// expected Response.
|
||||
expectedRespStatus int
|
||||
}{
|
||||
{bucketName, credentials.AccessKeyID, credentials.SecretAccessKey, http.StatusNoContent},
|
||||
}
|
||||
|
||||
// Iterating over the cases and writing the bucket policy.
|
||||
// its required to write the policies first before running tests on GetBucketPolicy.
|
||||
for i, testPolicy := range putTestPolicies {
|
||||
// obtain the put bucket policy request body.
|
||||
bucketPolicyStr := fmt.Sprintf(bucketPolicyTemplate, testPolicy.bucketName, testPolicy.bucketName)
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
rec := httptest.NewRecorder()
|
||||
// construct HTTP request for PUT bucket policy endpoint.
|
||||
req, err := newTestSignedRequest("PUT", getPutPolicyURL("", testPolicy.bucketName),
|
||||
int64(len(bucketPolicyStr)), bytes.NewReader([]byte(bucketPolicyStr)), testPolicy.accessKey, testPolicy.secretKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, err)
|
||||
}
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler.
|
||||
// Call the ServeHTTP to execute the handler.
|
||||
apiRouter.ServeHTTP(rec, req)
|
||||
if rec.Code != testPolicy.expectedRespStatus {
|
||||
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testPolicy.expectedRespStatus, rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// test cases with inputs and expected result for GetBucketPolicyHandler.
|
||||
testCases := []struct {
|
||||
bucketName string
|
||||
accessKey string
|
||||
secretKey string
|
||||
// expected output.
|
||||
expectedBucketPolicy string
|
||||
expectedRespStatus int
|
||||
}{
|
||||
{bucketName, credentials.AccessKeyID, credentials.SecretAccessKey, bucketPolicyTemplate, http.StatusOK},
|
||||
}
|
||||
// Iterating over the cases, fetching the policy and validating the response.
|
||||
for i, testCase := range testCases {
|
||||
// expected bucket policy json string.
|
||||
expectedBucketPolicyStr := fmt.Sprintf(testCase.expectedBucketPolicy, testCase.bucketName, testCase.bucketName)
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
rec := httptest.NewRecorder()
|
||||
// construct HTTP request for PUT bucket policy endpoint.
|
||||
req, err := newTestSignedRequest("GET", getGetPolicyURL("", testCase.bucketName),
|
||||
0, nil, testCase.accessKey, testCase.secretKey)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: Failed to create HTTP request for GetBucketPolicyHandler: <ERROR> %v", i+1, err)
|
||||
}
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
|
||||
// Call the ServeHTTP to execute the handler, GetBucketPolicyHandler handles the request.
|
||||
apiRouter.ServeHTTP(rec, req)
|
||||
// Assert the response code with the expected status.
|
||||
if rec.Code != testCase.expectedRespStatus {
|
||||
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, rec.Code)
|
||||
}
|
||||
// read the response body.
|
||||
bucketPolicyReadBuf, err := ioutil.ReadAll(rec.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: %s: Failed parsing response body: <ERROR> %v", i+1, instanceType, err)
|
||||
}
|
||||
// Verify whether the bucket policy fetched is same as the one inserted.
|
||||
if expectedBucketPolicyStr != string(bucketPolicyReadBuf) {
|
||||
t.Errorf("Test %d: %s: Bucket policy differs from expected value.", i+1, instanceType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapper for calling Delete Bucket Policy HTTP handler tests for both XL multiple disks and single node setup.
|
||||
func TestDeleteBucketPolicyHandler(t *testing.T) {
|
||||
ExecObjectLayerTest(t, testDeleteBucketPolicyHandler)
|
||||
}
|
||||
|
||||
// testDeleteBucketPolicyHandler - Test for Delete bucket policy end point.
|
||||
// TODO: Add exhaustive cases with various combination of statement fields.
|
||||
func testDeleteBucketPolicyHandler(obj ObjectLayer, instanceType string, t TestErrHandler) {
|
||||
initBucketPolicies(obj)
|
||||
|
||||
// get random bucket name.
|
||||
bucketName := getRandomBucketName()
|
||||
// Create bucket.
|
||||
err := obj.MakeBucket(bucketName)
|
||||
if err != nil {
|
||||
// failed to create newbucket, abort.
|
||||
t.Fatalf("%s : %s", instanceType, err)
|
||||
}
|
||||
|
||||
// Register the API end points with XL/FS object layer.
|
||||
// Registering PutBucketPolicy and DeleteBucketPolicy handlers.
|
||||
apiRouter := initTestAPIEndPoints(obj, []string{"PutBucketPolicy", "DeleteBucketPolicy"})
|
||||
|
||||
// initialize the server and obtain the credentials and root.
|
||||
// credentials are necessary to sign the HTTP request.
|
||||
rootPath, err := newTestConfig("us-east-1")
|
||||
if err != nil {
|
||||
t.Fatalf("Init Test config failed")
|
||||
}
|
||||
// remove the root folder after the test ends.
|
||||
defer removeAll(rootPath)
|
||||
|
||||
credentials := serverConfig.GetCredential()
|
||||
|
||||
// template for constructing HTTP request body for PUT bucket policy.
|
||||
bucketPolicyTemplate := `{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Action": [
|
||||
"s3:GetBucketLocation",
|
||||
"s3:ListBucket"
|
||||
],
|
||||
"Effect": "Allow",
|
||||
"Principal": {
|
||||
"AWS": [
|
||||
"*"
|
||||
]
|
||||
},
|
||||
"Resource": [
|
||||
"arn:aws:s3:::%s"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Action": [
|
||||
"s3:GetObject"
|
||||
],
|
||||
"Effect": "Allow",
|
||||
"Principal": {
|
||||
"AWS": [
|
||||
"*"
|
||||
]
|
||||
},
|
||||
"Resource": [
|
||||
"arn:aws:s3:::%s/this*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
// Writing bucket policy before running test on DeleteBucketPolicy.
|
||||
putTestPolicies := []struct {
|
||||
bucketName string
|
||||
accessKey string
|
||||
secretKey string
|
||||
// expected Response.
|
||||
expectedRespStatus int
|
||||
}{
|
||||
{bucketName, credentials.AccessKeyID, credentials.SecretAccessKey, http.StatusNoContent},
|
||||
}
|
||||
|
||||
// Iterating over the cases and writing the bucket policy.
|
||||
// its required to write the policies first before running tests on GetBucketPolicy.
|
||||
for i, testPolicy := range putTestPolicies {
|
||||
// obtain the put bucket policy request body.
|
||||
bucketPolicyStr := fmt.Sprintf(bucketPolicyTemplate, testPolicy.bucketName, testPolicy.bucketName)
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
rec := httptest.NewRecorder()
|
||||
// construct HTTP request for PUT bucket policy endpoint.
|
||||
req, err := newTestSignedRequest("PUT", getPutPolicyURL("", testPolicy.bucketName),
|
||||
int64(len(bucketPolicyStr)), bytes.NewReader([]byte(bucketPolicyStr)), testPolicy.accessKey, testPolicy.secretKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, err)
|
||||
}
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
|
||||
// Call the ServeHTTP to execute the handler.
|
||||
apiRouter.ServeHTTP(rec, req)
|
||||
if rec.Code != testPolicy.expectedRespStatus {
|
||||
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testPolicy.expectedRespStatus, rec.Code)
|
||||
}
|
||||
}
|
||||
// testcases with input and expected output for DeleteBucketPolicyHandler.
|
||||
testCases := []struct {
|
||||
bucketName string
|
||||
accessKey string
|
||||
secretKey string
|
||||
// expected Response.
|
||||
expectedRespStatus int
|
||||
}{
|
||||
{bucketName, credentials.AccessKeyID, credentials.SecretAccessKey, http.StatusNoContent},
|
||||
}
|
||||
// Iterating over the cases and deleting the bucket policy and then asserting response.
|
||||
for i, testCase := range testCases {
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
rec := httptest.NewRecorder()
|
||||
// construct HTTP request for Delete bucket policy endpoint.
|
||||
req, err := newTestSignedRequest("DELETE", getDeletePolicyURL("", testCase.bucketName),
|
||||
0, nil, testCase.accessKey, testCase.secretKey)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: Failed to create HTTP request for GetBucketPolicyHandler: <ERROR> %v", i+1, err)
|
||||
}
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
|
||||
// Call the ServeHTTP to execute the handler, DeleteBucketPolicyHandler handles the request.
|
||||
apiRouter.ServeHTTP(rec, req)
|
||||
// Assert the response code with the expected status.
|
||||
if rec.Code != testCase.expectedRespStatus {
|
||||
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, rec.Code)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const policyJSON = "policy.json"
|
||||
|
||||
func getBucketFromPolicyPath(oldPolicyPath string) string {
|
||||
bucketPrefix, _ := filepath.Split(oldPolicyPath)
|
||||
_, bucketName := filepath.Split(strings.TrimSuffix(bucketPrefix, slashSeparator))
|
||||
return bucketName
|
||||
}
|
||||
|
||||
func cleanupOldBucketPolicyConfigs() error {
|
||||
// Get old bucket policy config directory.
|
||||
oldBucketsConfigDir, err := getOldBucketsConfigPath()
|
||||
fatalIf(err, "Unable to fetch buckets config path to migrate bucket policy")
|
||||
|
||||
// Recursively remove configDir/buckets/ - old bucket policy config location.
|
||||
// N B This is called only if all bucket policies were successfully migrated.
|
||||
return os.RemoveAll(oldBucketsConfigDir)
|
||||
}
|
||||
|
||||
func migrateBucketPolicyConfig(objAPI ObjectLayer) error {
|
||||
// Get old bucket policy config directory.
|
||||
oldBucketsConfigDir, err := getOldBucketsConfigPath()
|
||||
fatalIf(err, "Unable to fetch buckets config path to migrate bucket policy")
|
||||
|
||||
// Check if config directory holding bucket policy exists before
|
||||
// migration.
|
||||
_, err = os.Stat(oldBucketsConfigDir)
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// WalkFunc that migrates access-policy.json to
|
||||
// .minio.sys/buckets/bucketName/policy.json on all disks.
|
||||
migrateBucketPolicy := func(policyPath string, fileInfo os.FileInfo, err error) error {
|
||||
// policyFile - e.g /configDir/sample-bucket/access-policy.json
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Skip entries that aren't bucket policy files.
|
||||
if fileInfo.Name() != "access-policy.json" {
|
||||
return nil
|
||||
}
|
||||
// Get bucketName from old policy file path.
|
||||
bucketName := getBucketFromPolicyPath(policyPath)
|
||||
// Read bucket policy config from old location.
|
||||
policyBytes, err := ioutil.ReadFile(policyPath)
|
||||
fatalIf(err, "Unable to read bucket policy to migrate bucket policy", policyPath)
|
||||
newPolicyPath := retainSlash(bucketConfigPrefix) + retainSlash(bucketName) + policyJSON
|
||||
// Erasure code the policy config to all the disks.
|
||||
_, err = objAPI.PutObject(minioMetaBucket, newPolicyPath, int64(len(policyBytes)), bytes.NewReader(policyBytes), nil)
|
||||
fatalIf(err, "Unable to write bucket policy during migration.", newPolicyPath)
|
||||
return nil
|
||||
}
|
||||
return filepath.Walk(oldBucketsConfigDir, migrateBucketPolicy)
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"path"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Variable represents bucket policies in memory.
|
||||
var globalBucketPolicies *bucketPolicies
|
||||
|
||||
// Global bucket policies list, policies are enforced on each bucket looking
|
||||
// through the policies here.
|
||||
type bucketPolicies struct {
|
||||
rwMutex *sync.RWMutex
|
||||
|
||||
// Collection of 'bucket' policies.
|
||||
bucketPolicyConfigs map[string]*bucketPolicy
|
||||
}
|
||||
|
||||
// Fetch bucket policy for a given bucket.
|
||||
func (bp bucketPolicies) GetBucketPolicy(bucket string) *bucketPolicy {
|
||||
bp.rwMutex.RLock()
|
||||
defer bp.rwMutex.RUnlock()
|
||||
return bp.bucketPolicyConfigs[bucket]
|
||||
}
|
||||
|
||||
// Set a new bucket policy for a bucket, this operation will overwrite
|
||||
// any previous bucketpolicies for the bucket.
|
||||
func (bp *bucketPolicies) SetBucketPolicy(bucket string, policy *bucketPolicy) error {
|
||||
bp.rwMutex.Lock()
|
||||
defer bp.rwMutex.Unlock()
|
||||
if policy == nil {
|
||||
return errors.New("invalid argument")
|
||||
}
|
||||
bp.bucketPolicyConfigs[bucket] = policy
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove bucket policy for a bucket, from in-memory map.
|
||||
func (bp *bucketPolicies) RemoveBucketPolicy(bucket string) {
|
||||
bp.rwMutex.Lock()
|
||||
defer bp.rwMutex.Unlock()
|
||||
delete(bp.bucketPolicyConfigs, bucket)
|
||||
}
|
||||
|
||||
// Loads all bucket policies from persistent layer.
|
||||
func loadAllBucketPolicies(objAPI ObjectLayer) (policies map[string]*bucketPolicy, err error) {
|
||||
// List buckets to proceed loading all notification configuration.
|
||||
buckets, err := objAPI.ListBuckets()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
policies = make(map[string]*bucketPolicy)
|
||||
// Loads bucket policy.
|
||||
for _, bucket := range buckets {
|
||||
var policy *bucketPolicy
|
||||
policy, err = readBucketPolicy(bucket.Name, objAPI)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case BucketPolicyNotFound:
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
policies[bucket.Name] = policy
|
||||
}
|
||||
|
||||
// Success.
|
||||
return policies, nil
|
||||
|
||||
}
|
||||
|
||||
// Intialize all bucket policies.
|
||||
func initBucketPolicies(objAPI ObjectLayer) error {
|
||||
if objAPI == nil {
|
||||
return errInvalidArgument
|
||||
}
|
||||
// Read all bucket policies.
|
||||
policies, err := loadAllBucketPolicies(objAPI)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
globalBucketPolicies = &bucketPolicies{
|
||||
rwMutex: &sync.RWMutex{},
|
||||
bucketPolicyConfigs: policies,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getOldBucketsConfigPath - get old buckets config path. (Only used for migrating old bucket policies)
|
||||
func getOldBucketsConfigPath() (string, error) {
|
||||
configPath, err := getConfigPath()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return path.Join(configPath, "buckets"), nil
|
||||
}
|
||||
|
||||
// readBucketPolicy - reads bucket policy for an input bucket, returns BucketPolicyNotFound
|
||||
// if bucket policy is not found. This function also parses the bucket policy into an object.
|
||||
func readBucketPolicy(bucket string, objAPI ObjectLayer) (*bucketPolicy, error) {
|
||||
// Verify bucket is valid.
|
||||
if !IsValidBucketName(bucket) {
|
||||
return nil, BucketNameInvalid{Bucket: bucket}
|
||||
}
|
||||
policyPath := pathJoin(bucketConfigPrefix, bucket, policyJSON)
|
||||
objInfo, err := objAPI.GetObjectInfo(minioMetaBucket, policyPath)
|
||||
if err != nil {
|
||||
if _, ok := err.(ObjectNotFound); ok {
|
||||
return nil, BucketPolicyNotFound{Bucket: bucket}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
var buffer bytes.Buffer
|
||||
err = objAPI.GetObject(minioMetaBucket, policyPath, 0, objInfo.Size, &buffer)
|
||||
if err != nil {
|
||||
if _, ok := err.(ObjectNotFound); ok {
|
||||
return nil, BucketPolicyNotFound{Bucket: bucket}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
// Parse the saved policy.
|
||||
var policy = &bucketPolicy{}
|
||||
err = parseBucketPolicy(&buffer, policy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
}
|
||||
return policy, nil
|
||||
}
|
||||
|
||||
// removeBucketPolicy - removes any previously written bucket policy. Returns BucketPolicyNotFound
|
||||
// if no policies are found.
|
||||
func removeBucketPolicy(bucket string, objAPI ObjectLayer) error {
|
||||
// Verify bucket is valid.
|
||||
if !IsValidBucketName(bucket) {
|
||||
return BucketNameInvalid{Bucket: bucket}
|
||||
}
|
||||
|
||||
policyPath := pathJoin(bucketConfigPrefix, bucket, policyJSON)
|
||||
if err := objAPI.DeleteObject(minioMetaBucket, policyPath); err != nil {
|
||||
if _, ok := err.(ObjectNotFound); ok {
|
||||
return BucketPolicyNotFound{Bucket: bucket}
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeBucketPolicy - save all bucket policies.
|
||||
func writeBucketPolicy(bucket string, objAPI ObjectLayer, reader io.Reader, size int64) error {
|
||||
// Verify if bucket path legal
|
||||
if !IsValidBucketName(bucket) {
|
||||
return BucketNameInvalid{Bucket: bucket}
|
||||
}
|
||||
|
||||
policyPath := pathJoin(bucketConfigPrefix, bucket, policyJSON)
|
||||
_, err := objAPI.PutObject(minioMetaBucket, policyPath, size, reader, nil)
|
||||
return err
|
||||
}
|
||||
@@ -23,7 +23,7 @@ _init() {
|
||||
fi
|
||||
|
||||
# List of supported architectures
|
||||
SUPPORTED_OSARCH='linux/386 linux/amd64 linux/arm windows/386 windows/amd64 darwin/amd64 freebsd/amd64'
|
||||
SUPPORTED_OSARCH='linux/386 linux/amd64 linux/arm linux/arm64 windows/386 windows/amd64 darwin/amd64 freebsd/amd64'
|
||||
|
||||
## System binaries
|
||||
CP=`which cp`
|
||||
@@ -47,18 +47,54 @@ go_build() {
|
||||
release_shasum="$release_str/$os-$arch/$(basename $package).shasum"
|
||||
|
||||
# Go build to build the binary.
|
||||
GOOS=$os GOARCH=$arch go build --ldflags "${LDFLAGS}" -o $release_bin
|
||||
if [ "${arch}" == "arm" ]; then
|
||||
# Release binary downloadable name
|
||||
release_real_bin_6="$release_str/$os-${arch}6vl/$(basename $package)"
|
||||
|
||||
# Create copy
|
||||
if [ $os == "windows" ]; then
|
||||
$CP -p $release_bin ${release_real_bin}.exe
|
||||
release_bin_6="$release_str/$os-${arch}6vl/$(basename $package).$release_tag"
|
||||
## Support building for ARM6vl
|
||||
GOARM=6 GOOS=$os GOARCH=$arch go build --ldflags "${LDFLAGS}" -o $release_bin_6
|
||||
|
||||
## Copy
|
||||
$CP -p $release_bin_6 $release_real_bin_6
|
||||
|
||||
# Release shasum name
|
||||
release_shasum_6="$release_str/$os-${arch}6vl/$(basename $package).shasum"
|
||||
|
||||
# Calculate shasum
|
||||
shasum_str=$(${SHASUM} ${release_bin_6})
|
||||
echo ${shasum_str} | $SED "s/$release_str\/$os-${arch}6vl\///g" > $release_shasum_6
|
||||
|
||||
# Release binary downloadable name
|
||||
release_real_bin_7="$release_str/$os-$arch/$(basename $package)"
|
||||
|
||||
release_bin_7="$release_str/$os-$arch/$(basename $package).$release_tag"
|
||||
## Support building for ARM7vl
|
||||
GOARM=7 GOOS=$os GOARCH=$arch go build --ldflags "${LDFLAGS}" -o $release_bin_7
|
||||
|
||||
## Copy
|
||||
$CP -p $release_bin_7 $release_real_bin_7
|
||||
|
||||
# Release shasum name
|
||||
release_shasum_7="$release_str/$os-$arch/$(basename $package).shasum"
|
||||
|
||||
# Calculate shasum
|
||||
shasum_str=$(${SHASUM} ${release_bin_7})
|
||||
echo ${shasum_str} | $SED "s/$release_str\/$os-$arch\///g" > $release_shasum_7
|
||||
else
|
||||
$CP -p $release_bin $release_real_bin
|
||||
fi
|
||||
GOOS=$os GOARCH=$arch go build --ldflags "${LDFLAGS}" -o $release_bin
|
||||
|
||||
# Calculate shasum
|
||||
shasum_str=$(${SHASUM} ${release_bin})
|
||||
echo ${shasum_str} | $SED "s/$release_str\/$os-$arch\///g" > $release_shasum
|
||||
# Create copy
|
||||
if [ $os == "windows" ]; then
|
||||
$CP -p $release_bin ${release_real_bin}.exe
|
||||
else
|
||||
$CP -p $release_bin $release_real_bin
|
||||
fi
|
||||
|
||||
# Calculate shasum
|
||||
shasum_str=$(${SHASUM} ${release_bin})
|
||||
echo ${shasum_str} | $SED "s/$release_str\/$os-$arch\///g" > $release_shasum
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
@@ -70,12 +106,14 @@ main() {
|
||||
done
|
||||
|
||||
read -p "If you want to build for all, Just press Enter: " chosen_osarch
|
||||
if [ "$chosen_osarch" = "" ]; then
|
||||
if [ "$chosen_osarch" = "" ] || [ "$chosen_osarch" = "all" ]; then
|
||||
for each_osarch in ${SUPPORTED_OSARCH}; do
|
||||
go_build ${each_osarch}
|
||||
done
|
||||
else
|
||||
go_build ${chosen_osarch}
|
||||
for each_osarch in $(echo $chosen_osarch | sed 's/,/ /g'); do
|
||||
go_build ${each_osarch}
|
||||
done
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Minio Cloud Storage, (C) 2015 Minio, Inc.
|
||||
# Minio Cloud Storage, (C) 2014-2016 Minio, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -21,14 +21,21 @@ _init() {
|
||||
|
||||
## Minimum required versions for build dependencies
|
||||
GIT_VERSION="1.0"
|
||||
GO_VERSION="1.6"
|
||||
GO_VERSION="1.7.1"
|
||||
OSX_VERSION="10.8"
|
||||
UNAME=$(uname -sm)
|
||||
|
||||
## Check all dependencies are present
|
||||
MISSING=""
|
||||
KNAME=$(uname -s)
|
||||
ARCH=$(uname -m)
|
||||
}
|
||||
|
||||
## FIXME:
|
||||
## In OSX, 'readlink -f' option does not exist, hence
|
||||
## we have our own readlink -f behaviour here.
|
||||
## Once OSX has the option, below function is good enough.
|
||||
##
|
||||
## readlink() {
|
||||
## return /bin/readlink -f "$1"
|
||||
## }
|
||||
##
|
||||
readlink() {
|
||||
TARGET_FILE=$1
|
||||
|
||||
@@ -50,172 +57,115 @@ readlink() {
|
||||
echo $RESULT
|
||||
}
|
||||
|
||||
###
|
||||
#
|
||||
# Takes two arguments
|
||||
# arg1: version number in `x.x.x` format
|
||||
# arg2: version number in `x.x.x` format
|
||||
#
|
||||
# example: check_version "$version1" "$version2"
|
||||
#
|
||||
# returns:
|
||||
# 0 - Installed version is equal to required
|
||||
# 1 - Installed version is greater than required
|
||||
# 2 - Installed version is lesser than required
|
||||
# 3 - If args have length zero
|
||||
#
|
||||
####
|
||||
check_version() {
|
||||
## validate args
|
||||
[[ -z "$1" ]] && return 3
|
||||
[[ -z "$2" ]] && return 3
|
||||
|
||||
if [[ $1 == $2 ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local IFS=.
|
||||
local i ver1=($1) ver2=($2)
|
||||
# fill empty fields in ver1 with zeros
|
||||
for ((i=${#ver1[@]}; i<${#ver2[@]}; i++)); do
|
||||
ver1[i]=0
|
||||
done
|
||||
for ((i=0; i<${#ver1[@]}; i++)); do
|
||||
if [[ -z ${ver2[i]} ]]; then
|
||||
# fill empty fields in ver2 with zeros
|
||||
ver2[i]=0
|
||||
fi
|
||||
if ((10#${ver1[i]} > 10#${ver2[i]})); then
|
||||
## FIXME:
|
||||
## In OSX, 'sort -V' option does not exist, hence
|
||||
## we have our own version compare function.
|
||||
## Once OSX has the option, below function is good enough.
|
||||
##
|
||||
## check_minimum_version() {
|
||||
## versions=($(echo -e "$1\n$2" | sort -V))
|
||||
## return [ "$1" == "${versions[0]}" ]
|
||||
## }
|
||||
##
|
||||
check_minimum_version() {
|
||||
IFS='.' read -r -a varray1 <<< "$1"
|
||||
IFS='.' read -r -a varray2 <<< "$2"
|
||||
|
||||
for i in "${!varray1[@]}"; do
|
||||
if [[ ${varray1[i]} -lt ${varray2[i]} ]]; then
|
||||
return 0
|
||||
elif [[ ${varray1[i]} -gt ${varray2[i]} ]]; then
|
||||
return 1
|
||||
fi
|
||||
if ((10#${ver1[i]} < 10#${ver2[i]})); then
|
||||
## Installed version is lesser than required - Bad condition
|
||||
return 2
|
||||
fi
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
check_golang_env() {
|
||||
echo ${GOROOT:?} 2>&1 >/dev/null
|
||||
if [ $? -eq 1 ]; then
|
||||
echo "ERROR"
|
||||
echo "GOROOT environment variable missing, please refer to Go installation document"
|
||||
echo "https://github.com/minio/minio/blob/master/INSTALLGO.md#install-go-13"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ${GOPATH:?} 2>&1 >/dev/null
|
||||
if [ $? -eq 1 ]; then
|
||||
echo "ERROR"
|
||||
echo "GOPATH environment variable missing, please refer to Go installation document"
|
||||
echo "https://github.com/minio/minio/blob/master/INSTALLGO.md#install-go-13"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local go_binary_path=$(which go)
|
||||
|
||||
if [ -z "${go_binary_path}" ] ; then
|
||||
echo "Cannot find go binary in your PATH configuration, please refer to Go installation document"
|
||||
echo "https://github.com/minio/minio/blob/master/INSTALLGO.md#install-go-13"
|
||||
exit -1
|
||||
fi
|
||||
|
||||
local new_go_binary_path=${go_binary_path}
|
||||
if [ -h "${go_binary_path}" ]; then
|
||||
new_go_binary_path=$(readlink ${go_binary_path})
|
||||
fi
|
||||
|
||||
if [[ !"$(dirname ${new_go_binary_path})" =~ *"${GOROOT%%*(/)}"* ]] ; then
|
||||
echo "The go binary found in your PATH configuration does not belong to the Go installation pointed by your GOROOT environment," \
|
||||
"please refer to Go installation document"
|
||||
echo "https://github.com/minio/minio/blob/master/INSTALLGO.md#install-go-13"
|
||||
exit -1
|
||||
fi
|
||||
}
|
||||
|
||||
is_supported_os() {
|
||||
case ${UNAME%% *} in
|
||||
"Linux")
|
||||
os="linux"
|
||||
;;
|
||||
"FreeBSD")
|
||||
os="freebsd"
|
||||
;;
|
||||
"Darwin")
|
||||
osx_host_version=$(env sw_vers -productVersion)
|
||||
check_version "${osx_host_version}" "${OSX_VERSION}"
|
||||
[[ $? -ge 2 ]] && die "Minimum OSX version supported is ${OSX_VERSION}"
|
||||
;;
|
||||
"*")
|
||||
echo "Exiting.. unsupported operating system found"
|
||||
exit 1;
|
||||
esac
|
||||
}
|
||||
|
||||
is_supported_arch() {
|
||||
local supported
|
||||
case ${UNAME##* } in
|
||||
"x86_64" | "amd64")
|
||||
supported=1
|
||||
;;
|
||||
"arm"*)
|
||||
supported=1
|
||||
assert_is_supported_arch() {
|
||||
case "${ARCH}" in
|
||||
x86_64 | amd64 | aarch64 | arm* )
|
||||
return
|
||||
;;
|
||||
*)
|
||||
supported=0
|
||||
;;
|
||||
echo "ERROR"
|
||||
echo "Arch '${ARCH}' not supported."
|
||||
echo "Supported Arch: [x86_64, amd64, aarch64, arm*]"
|
||||
exit 1
|
||||
esac
|
||||
if [ $supported -eq 0 ]; then
|
||||
echo "Invalid arch: ${UNAME} not supported, please use x86_64/amd64"
|
||||
exit 1;
|
||||
fi
|
||||
}
|
||||
|
||||
check_deps() {
|
||||
check_version "$(env go version 2>/dev/null | sed 's/^.* go\([0-9.]*\).*$/\1/')" "${GO_VERSION}"
|
||||
if [ $? -ge 2 ]; then
|
||||
MISSING="${MISSING} golang(${GO_VERSION})"
|
||||
assert_is_supported_os() {
|
||||
case "${KNAME}" in
|
||||
Linux | FreeBSD | OpenBSD | NetBSD | DragonFly )
|
||||
return
|
||||
;;
|
||||
Darwin )
|
||||
osx_host_version=$(env sw_vers -productVersion)
|
||||
if ! check_minimum_version "${OSX_VERSION}" "${osx_host_version}"; then
|
||||
echo "ERROR"
|
||||
echo "OSX version '${osx_host_version}' not supported."
|
||||
echo "Minimum supported version: ${OSX_VERSION}"
|
||||
exit 1
|
||||
fi
|
||||
return
|
||||
;;
|
||||
*)
|
||||
echo "ERROR"
|
||||
echo "OS '${KNAME}' is not supported."
|
||||
echo "Supported OS: [Linux, FreeBSD, OpenBSD, NetBSD, Darwin, DragonFly]"
|
||||
exit 1
|
||||
esac
|
||||
}
|
||||
|
||||
assert_check_golang_env() {
|
||||
if ! which go >/dev/null 2>&1; then
|
||||
echo "ERROR"
|
||||
echo "Cannot find go binary in your PATH configuration, please refer to Go installation document at"
|
||||
echo "https://docs.minio.io/docs/how-to-install-golang"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
check_version "$(env git --version 2>/dev/null | sed -e 's/^.* \([0-9.\].*\).*$/\1/' -e 's/^\([0-9.\]*\).*/\1/g')" "${GIT_VERSION}"
|
||||
if [ $? -ge 2 ]; then
|
||||
MISSING="${MISSING} git"
|
||||
installed_go_version=$(go version | sed 's/^.* go\([0-9.]*\).*$/\1/')
|
||||
if ! check_minimum_version "${GO_VERSION}" "${installed_go_version}"; then
|
||||
echo "ERROR"
|
||||
echo "Go version '${installed_go_version}' not supported."
|
||||
echo "Minimum supported version: ${GO_VERSION}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "${GOPATH}" ]; then
|
||||
echo "ERROR"
|
||||
echo "GOPATH environment variable missing, please refer to Go installation document"
|
||||
echo "https://docs.minio.io/docs/how-to-install-golang"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
assert_check_deps() {
|
||||
# support unusual Git versions such as: 2.7.4 (Apple Git-66)
|
||||
installed_git_version=$(git version | perl -ne '$_ =~ m/git version (.*?)( |$)/; print "$1\n";')
|
||||
if ! check_minimum_version "${GIT_VERSION}" "${installed_git_version}"; then
|
||||
echo "ERROR"
|
||||
echo "Git version '${installed_git_version}' not supported."
|
||||
echo "Minimum supported version: ${GIT_VERSION}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
echo -n "Check for supported arch.. "
|
||||
is_supported_arch
|
||||
## Check for supported arch
|
||||
assert_is_supported_arch
|
||||
|
||||
echo -n "Check for supported os.. "
|
||||
is_supported_os
|
||||
## Check for supported os
|
||||
assert_is_supported_os
|
||||
|
||||
echo -n "Checking if proper environment variables are set.. "
|
||||
check_golang_env
|
||||
## Check for Go environment
|
||||
assert_check_golang_env
|
||||
|
||||
echo "Done"
|
||||
echo "Using GOPATH=${GOPATH} and GOROOT=${GOROOT}"
|
||||
|
||||
echo -n "Checking dependencies for Minio.. "
|
||||
check_deps
|
||||
|
||||
## If dependencies are missing, warn the user and abort
|
||||
if [ "x${MISSING}" != "x" ]; then
|
||||
echo "ERROR"
|
||||
echo
|
||||
echo "The following build tools are missing:"
|
||||
echo
|
||||
echo "** ${MISSING} **"
|
||||
echo
|
||||
echo "Please install them "
|
||||
echo "${MISSING}"
|
||||
echo
|
||||
echo "Follow https://docs.minio.io/docs/how-to-install-golang for further instructions"
|
||||
exit 1
|
||||
fi
|
||||
echo "Done"
|
||||
## Check for dependencies
|
||||
assert_check_deps
|
||||
}
|
||||
|
||||
_init && main "$@"
|
||||
|
||||
@@ -15,26 +15,23 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
_init() {
|
||||
|
||||
shopt -s extglob
|
||||
|
||||
PWD=$(pwd -P)
|
||||
GOPATH=$(cd "$(go env GOPATH)" ; env pwd -P)
|
||||
}
|
||||
|
||||
main() {
|
||||
echo "Checking if project is at ${GOPATH}"
|
||||
for minio in $(echo ${GOPATH} | tr ':' ' '); do
|
||||
if [ ! -d ${minio}/src/github.com/minio/minio ]; then
|
||||
echo "Project not found in ${minio}, please follow instructions provided at https://github.com/minio/minio/blob/master/CONTRIBUTING.md#setup-your-minio-github-repository" \
|
||||
&& exit 1
|
||||
fi
|
||||
if [ "x${minio}/src/github.com/minio/minio" != "x${PWD}" ]; then
|
||||
echo "Build outside of ${minio}, two source checkouts found. Exiting." && exit 1
|
||||
echo "Checking project is in GOPATH:"
|
||||
|
||||
IFS=':' read -r -a paths <<< "$GOPATH"
|
||||
for path in "${paths[@]}"; do
|
||||
minio_path="$path/src/github.com/minio/minio"
|
||||
if [ -d "$minio_path" ]; then
|
||||
if [ "$minio_path" -ef "$PWD" ]; then
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo "ERROR"
|
||||
echo "Project not found in ${GOPATH}."
|
||||
echo "Follow instructions at https://github.com/minio/minio/blob/master/CONTRIBUTING.md#setup-your-minio-github-repository"
|
||||
exit 1
|
||||
}
|
||||
|
||||
_init && main
|
||||
|
||||
main
|
||||
|
||||
@@ -28,11 +28,11 @@ import (
|
||||
|
||||
func genLDFlags(version string) string {
|
||||
var ldflagsStr string
|
||||
ldflagsStr = "-X main.minioVersion=" + version
|
||||
ldflagsStr += " -X main.minioReleaseTag=" + releaseTag(version)
|
||||
ldflagsStr += " -X main.minioCommitID=" + commitID()
|
||||
ldflagsStr += " -X main.minioShortCommitID=" + commitID()[:12]
|
||||
ldflagsStr += " -X main.minioGOPATH=" + os.Getenv("GOPATH")
|
||||
ldflagsStr = "-X github.com/minio/minio/cmd.Version=" + version
|
||||
ldflagsStr += " -X github.com/minio/minio/cmd.ReleaseTag=" + releaseTag(version)
|
||||
ldflagsStr += " -X github.com/minio/minio/cmd.CommitID=" + commitID()
|
||||
ldflagsStr += " -X github.com/minio/minio/cmd.ShortCommitID=" + commitID()[:12]
|
||||
ldflagsStr += " -X github.com/minio/minio/cmd.GOPATH=" + os.Getenv("GOPATH")
|
||||
return ldflagsStr
|
||||
}
|
||||
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// createCertsPath create certs path.
|
||||
func createCertsPath() error {
|
||||
certsPath, err := getCertsPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.MkdirAll(certsPath, 0700)
|
||||
}
|
||||
|
||||
// getCertsPath get certs path.
|
||||
func getCertsPath() (string, error) {
|
||||
var certsPath string
|
||||
configDir, err := getConfigPath()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
certsPath = filepath.Join(configDir, globalMinioCertsDir)
|
||||
return certsPath, nil
|
||||
}
|
||||
|
||||
// mustGetCertsPath must get certs path.
|
||||
func mustGetCertsPath() string {
|
||||
certsPath, err := getCertsPath()
|
||||
fatalIf(err, "Failed to get certificate path.")
|
||||
return certsPath
|
||||
}
|
||||
|
||||
// mustGetCertFile must get cert file.
|
||||
func mustGetCertFile() string {
|
||||
return filepath.Join(mustGetCertsPath(), globalMinioCertFile)
|
||||
}
|
||||
|
||||
// mustGetKeyFile must get key file.
|
||||
func mustGetKeyFile() string {
|
||||
return filepath.Join(mustGetCertsPath(), globalMinioKeyFile)
|
||||
}
|
||||
|
||||
// isCertFileExists verifies if cert file exists, returns true if
|
||||
// found, false otherwise.
|
||||
func isCertFileExists() bool {
|
||||
st, e := os.Stat(filepath.Join(mustGetCertsPath(), globalMinioCertFile))
|
||||
// If file exists and is regular return true.
|
||||
if e == nil && st.Mode().IsRegular() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isKeyFileExists verifies if key file exists, returns true if found,
|
||||
// false otherwise.
|
||||
func isKeyFileExists() bool {
|
||||
st, e := os.Stat(filepath.Join(mustGetCertsPath(), globalMinioKeyFile))
|
||||
// If file exists and is regular return true.
|
||||
if e == nil && st.Mode().IsRegular() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isSSL - returns true with both cert and key exists.
|
||||
func isSSL() bool {
|
||||
if isCertFileExists() && isKeyFileExists() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||