diff --git a/.github/workflows/chrome.yml b/.github/workflows/chrome.yml index 49e1cd957..d7a8aa728 100644 --- a/.github/workflows/chrome.yml +++ b/.github/workflows/chrome.yml @@ -9,7 +9,7 @@ jobs: matrix: os: [ubuntu-latest] python-version: [3.7] - node: ['12'] + node: ['14'] runs-on: ${{ matrix.os }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 57a20b4ba..e60227724 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -9,7 +9,7 @@ jobs: matrix: os: [ubuntu-latest] python-version: [3.7] - node: ['12'] + node: ['14'] runs-on: ${{ matrix.os }} diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml index 7f00f9781..152e39dbd 100644 --- a/.github/workflows/eslint.yml +++ b/.github/workflows/eslint.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [ '12', '14' ] + node: ['14'] name: Node ${{ matrix.node }} eslint check steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/firefox.yml b/.github/workflows/firefox.yml index 744828751..881d00707 100644 --- a/.github/workflows/firefox.yml +++ b/.github/workflows/firefox.yml @@ -9,7 +9,7 @@ jobs: matrix: os: [ubuntu-latest] python-version: [3.7] - node: ['12'] + node: ['14'] runs-on: ${{ matrix.os }} diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index f08dd8a0f..9416e8173 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -9,7 +9,7 @@ jobs: matrix: os: [ubuntu-latest] python-version: [3.7, 3.8] - node: ['12'] + node: ['14'] runs-on: ${{ matrix.os }} diff --git a/docs/source/conf.py b/docs/source/conf.py index a09ba77d8..8a97d6a3e 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -28,7 +28,7 @@ author = 'CSC Developers' # The full version, including alpha/beta/rc tags -version = release = '1.0.0rc12' +version = release = '1.0.0rc13' # -- General configuration --------------------------------------------------- diff --git a/docs/source/instructions.rst b/docs/source/instructions.rst index f391010a9..1f025a190 100644 --- a/docs/source/instructions.rst +++ b/docs/source/instructions.rst @@ -63,6 +63,8 @@ Variables are depicted in the table below: +---------------------------------------------+---------+---------------------------------------------------------------+--+--+ | ``BROWSER_START_REQUEST_INT_ENDPOINT_URL`` | | internal URL / hostname of the request API | | | +---------------------------------------------+---------+---------------------------------------------------------------+--+--+ +| ``LOG_LEVEL`` | | set logging level e.g. INFO, DEBUG | | | ++---------------------------------------------+---------+---------------------------------------------------------------+--+--+ .. hint:: Authentication endpoint can also be specified with any openrc file, which can be usually downloaded from Openstack. The setup script diff --git a/swift_browser_ui/__init__.py b/swift_browser_ui/__init__.py index 4e71272f6..26c47fb94 100644 --- a/swift_browser_ui/__init__.py +++ b/swift_browser_ui/__init__.py @@ -7,6 +7,6 @@ __name__ = "swift_browser_ui" -__version__ = "1.0.0rc12" +__version__ = "1.0.0rc13" __author__ = "CSC Developers" __license__ = "MIT License" diff --git a/swift_browser_ui/api.py b/swift_browser_ui/api.py index bd5f89636..f2c8baea5 100644 --- a/swift_browser_ui/api.py +++ b/swift_browser_ui/api.py @@ -35,12 +35,17 @@ async def get_os_user( ) -def _unpack(item: dict, - cont: typing.List[dict], - request: aiohttp.web.Request) -> typing.Any: +def _unpack( + item: dict, + cont: typing.List[dict], + request: aiohttp.web.Request +) -> typing.Any: """Unpack container list if the request was successful.""" if item["success"]: - request.app['Log'].info("container list unpacked successfully") + tenant = f"Container: {item['container']}" \ + if item['container'] else "No tenant specified, container" + request.app['Log'].info(f"{tenant} " + "list unpacked successfully.") return cont.extend(item["listing"]) else: request.app['Log'].error(item["error"]) @@ -70,19 +75,23 @@ async def swift_list_buckets( # The maximum amount of buckets / containers is measured in thousands, # so it's not necessary to think twice about iterating over the whole # response at once - sess = request.app['Creds'][session]['ST_conn'].list() - [_unpack(i, cont, request) for i in sess] + serv = request.app['Creds'][session]['ST_conn'].list() + [_unpack(i, cont, request) for i in serv] # for a bucket with no objects if not cont: # return empty object + request.app['Log'].debug("Empty container list.") raise aiohttp.web.HTTPNotFound() - + return aiohttp.web.json_response(cont) except SwiftError: + request.app['Log'].error("SwiftError occured " + "return empty container list.") raise aiohttp.web.HTTPNotFound() except KeyError: # listing is missing; possible broken swift auth + request.app['Log'].error("listing is missing; " + "possible broken swift auth.") return aiohttp.web.json_response(cont) - return aiohttp.web.json_response(cont) async def swift_create_container( @@ -103,6 +112,7 @@ async def swift_create_container( request.match_info["container"] ) except (SwiftError, ClientException): + request.app['Log'].error("Container creation failed.") raise aiohttp.web.HTTPServerError( reason="Container creation failure" ) @@ -124,6 +134,7 @@ async def swift_list_objects( not necessary in this case and returns a JSON response containing all the necessasry data. """ + obj: typing.List[dict] = [] try: session = api_check(request) request.app['Log'].info( @@ -134,11 +145,11 @@ async def swift_list_objects( ) ) - obj: typing.List[dict] = [] - sess = request.app['Creds'][session]['ST_conn'].list( + serv = request.app['Creds'][session]['ST_conn'].list( container=request.query['bucket']) - [_unpack(i, obj, request) for i in sess] + [_unpack(i, obj, request) for i in serv] if not obj: + request.app['Log'].debug("Empty container object list.") raise aiohttp.web.HTTPNotFound() # Some tools leave unicode nulls to e.g. file hashes. These must be @@ -153,10 +164,14 @@ async def swift_list_objects( return aiohttp.web.json_response(obj) except SwiftError: - return aiohttp.web.json_response([]) + request.app['Log'].error("SwiftError occured " + "return empty container list.") + return aiohttp.web.json_response(obj) except KeyError: # listing is missing; possible broken swift auth - return aiohttp.web.json_response([]) + request.app['Log'].error("listing is missing; " + "possible broken swift auth.") + return aiohttp.web.json_response(obj) async def swift_list_shared_objects( @@ -169,6 +184,7 @@ async def swift_list_shared_objects( not necessary in this case and returns a JSON response containing all the necessary data. """ + obj: typing.List[dict] = [] try: session = api_check(request) request.app["Log"].info( @@ -183,12 +199,11 @@ async def swift_list_shared_objects( request.app["Creds"][session]["OS_sess"], url=request.query["storageurl"] ) + serv = tmp_serv.list(container=request.query['bucket']) + [_unpack(i, obj, request) for i in serv] - obj: typing.List[dict] = [] - list(map(lambda i: obj.extend(i["listing"]), # type: ignore - tmp_serv.list( - container=request.query["container"]))) if not obj: + request.app['Log'].debug("Empty list in shared container.") raise aiohttp.web.HTTPNotFound() # Some tools leave unicode nulls to e.g. file hashes. These must be @@ -204,13 +219,17 @@ async def swift_list_shared_objects( return aiohttp.web.json_response(obj) except SwiftError: - return aiohttp.web.json_response([]) + request.app['Log'].error("SwiftError occured " + "return empty container list.") + return aiohttp.web.json_response(obj) except ClientException as e: request.app['Log'].error(e.msg) - return aiohttp.web.json_response([]) + return aiohttp.web.json_response(obj) except KeyError: # listing is missing; possible broken swift auth - return aiohttp.web.json_response([]) + request.app['Log'].error("listing is missing; " + "possible broken swift auth.") + return aiohttp.web.json_response(obj) async def swift_download_object( @@ -264,6 +283,14 @@ async def swift_download_shared_object( ) -> aiohttp.web.Response: """Point a user to the shared download runner.""" session = api_check(request) + request.app['Log'].info( + 'API call for shared download runner ' + 'from {0}, sess: {1} :: {2}'.format( + request.remote, + session, + time.ctime(), + ) + ) project: str = request.match_info['project'] container: str = request.match_info['container'] @@ -297,6 +324,14 @@ async def swift_download_container( ) -> aiohttp.web.Response: """Point a user to the container download runner.""" session = api_check(request) + request.app['Log'].info( + 'API call for container download runner ' + 'from {0}, sess: {1} :: {2}'.format( + request.remote, + session, + time.ctime(), + ) + ) project: str = request.match_info['project'] container: str = request.match_info['container'] @@ -329,6 +364,13 @@ async def swift_upload_object_chunk( ) -> aiohttp.web.Response: """Point a user to the object upload runner.""" session = api_check(request) + request.app['Log'].info( + 'API call for object upload runner from {0}, sess: {1} :: {2}'.format( + request.remote, + session, + time.ctime(), + ) + ) project: str = request.match_info['project'] container: str = request.match_info['container'] @@ -356,10 +398,17 @@ async def swift_upload_object_chunk( async def swift_replicate_container( - request: aiohttp.web.Request + request: aiohttp.web.Request ) -> aiohttp.web.Response: """Point the user to container replication endpoint.""" session = api_check(request) + request.app['Log'].info( + 'API call for replication endpoint from {0}, sess: {1} :: {2}'.format( + request.remote, + session, + time.ctime(), + ) + ) project: str = request.match_info['project'] container: str = request.match_info['container'] @@ -394,6 +443,14 @@ async def swift_check_object_chunk( ) -> aiohttp.web.Response: """Point check for object existence to the upload runner.""" session = api_check(request) + request.app['Log'].info( + 'API call to check object existence in upload runner ' + 'from {0}, sess: {1} :: {2}'.format( + request.remote, + session, + time.ctime(), + ) + ) project: str = request.match_info['project'] container: str = request.match_info['container'] @@ -532,6 +589,7 @@ async def get_metadata_object( # contain sensitive data. This is not needed directly for the UI, but # the API is exposed for the user and thus can't expose any sensitive info if not meta_cont: + request.app['Log'].error("Container not specified.") raise aiohttp.web.HTTPClientError() conn = request.app['Creds'][session]['ST_conn'] @@ -551,25 +609,37 @@ async def get_project_metadata( # it contains e.g. temporary URL keys. These keys can be used to pull any # object from the object storage, and thus shouldn't be provided for the # user. - session = api_check(request) - request.app['Log'].info( - 'Api call for project metadata check from {0}, sess: {1}'.format( - request.remote, - session, + ret = dict() + try: + session = api_check(request) + request.app['Log'].info( + 'Api call for project metadata check from {0}, sess: {1}'.format( + request.remote, + session, + ) ) - ) - conn = request.app['Creds'][session]['ST_conn'] + conn = request.app['Creds'][session]['ST_conn'] - # Get the account metadata listing - ret = dict(conn.stat()['items']) - ret = { - 'Account': ret['Account'], - 'Containers': ret['Containers'], - 'Objects': ret['Objects'], - 'Bytes': ret['Bytes'], - } - return aiohttp.web.json_response(ret) + # Get the account metadata listing + stat = dict(conn.stat()['items']) + ret = { + 'Account': stat['Account'], + 'Containers': stat['Containers'], + 'Objects': stat['Objects'], + 'Bytes': stat['Bytes'], + } + return aiohttp.web.json_response(ret) + except SwiftError: + request.app['Log'].error("SwiftError occured.") + return aiohttp.web.json_response(ret) + except ClientException as e: + request.app['Log'].error(e.msg) + return aiohttp.web.json_response(ret) + except KeyError: + request.app['Log'].error("items is missing; possible allas " + "is not authorised for project.") + return aiohttp.web.json_response(ret) async def os_list_projects( @@ -614,6 +684,14 @@ async def get_shared_container_address( ) -> aiohttp.web.Response: """Get the project specific object storage address.""" session = api_check(request) + request.app['Log'].info( + 'API call for project specific storage ' + 'from {0}, sess: {1} :: {2}'.format( + request.remote, + session, + time.ctime(), + ) + ) sess = request.app['Creds'][session]['OS_sess'] host = sess.get_endpoint(service_type="object-store") @@ -625,14 +703,20 @@ async def get_access_control_metadata( ) -> aiohttp.web.Response: """Fetch a compilation of ACL information for sharing discovery.""" session = api_check(request) + request.app['Log'].info( + 'API call for project ACL info from {0}, sess: {1} :: {2}'.format( + request.remote, + session, + time.ctime(), + ) + ) serv = request.app['Creds'][session]['ST_conn'] sess = request.app['Creds'][session]['OS_sess'] # Get a list of containers containers: typing.List[dict] = [] - list(map(lambda i: containers.extend(i['listing']), # type: ignore - serv.list())) + [_unpack(i, containers, request) for i in serv] host = sess.get_endpoint(service_type="object-store") @@ -683,6 +767,13 @@ async def remove_project_container_acl( ) -> aiohttp.web.Response: """Remove access from a project in container acl.""" session = api_check(request) + request.app['Log'].info( + 'API call to remove container ACL from {0}, sess: {1} :: {2}'.format( + request.remote, + session, + time.ctime(), + ) + ) serv = request.app['Creds'][session]['ST_conn'] @@ -727,6 +818,14 @@ async def remove_container_acl( return await remove_project_container_acl(request) except KeyError: session = api_check(request) + request.app['Log'].info( + 'API call to remove projects fom container ' + 'ACL from {0}, sess: {1} :: {2}'.format( + request.remote, + session, + time.ctime(), + ) + ) serv = request.app['Creds'][session]['ST_conn'] @@ -752,12 +851,20 @@ async def add_project_container_acl( ) -> aiohttp.web.Response: """Add access for a project in container acl.""" session = api_check(request) - + request.app['Log'].info( + 'API call to add access for project in container ' + 'from {0}, sess: {1} :: {2}'.format( + request.remote, + session, + time.ctime(), + ) + ) serv = request.app['Creds'][session]['ST_conn'] container = request.match_info["container"] projects = request.query["projects"].split(",") - + request.app['Log'].debug(f"Requested container {container} " + f"and projects {projects}.") meta_headers = dict(serv.stat(container=container)["items"]) read_acl = meta_headers["Read ACL"] diff --git a/swift_browser_ui/settings.py b/swift_browser_ui/settings.py index 04b2f8a08..dcc034434 100644 --- a/swift_browser_ui/settings.py +++ b/swift_browser_ui/settings.py @@ -48,11 +48,7 @@ # Log from envvar as well, since the CLI shouldn't be the only option. # Semantics are identical with the frontend, so i.e.: -# BROWSER_VERBOSE (LOGLEVEL INFO), BROWSER_DEBUG (LOGLEVEL DEBUG) -if environ.get('BROWSER_VERBOSE', None): - logging.root.setLevel(logging.INFO) -if environ.get('BROWSER_DEBUG', None): - logging.root.setLevel(logging.DEBUG) +logging.root.setLevel(environ.get('LOG_LEVEL', 'INFO')) # The following is the variable containing the default settings, which will be # overloaded as necessary. diff --git a/swift_browser_ui_frontend/package.json b/swift_browser_ui_frontend/package.json index 0fd882249..b3a7a676b 100644 --- a/swift_browser_ui_frontend/package.json +++ b/swift_browser_ui_frontend/package.json @@ -1,6 +1,6 @@ { "name": "swift_browser_ui_frontend_npm", - "version": "1.0.0rc12", + "version": "1.0.0rc13", "private": true, "scripts": { "serve": "vue-cli-service serve", @@ -59,4 +59,4 @@ "> 3%", "last 2 versions" ] -} +} \ No newline at end of file diff --git a/swift_browser_ui_frontend/src/common/api.js b/swift_browser_ui_frontend/src/common/api.js index ebbd4fb40..63abe38b6 100644 --- a/swift_browser_ui_frontend/src/common/api.js +++ b/swift_browser_ui_frontend/src/common/api.js @@ -138,13 +138,18 @@ export async function getProjectMeta () { .then( function ( json_ret ) { let newRet = json_ret; newRet["Size"] = getHumanReadableSize(newRet["Bytes"]); + if (newRet["Bytes"] > 1099511627776) { + newRet["ProjectSize"] = newRet["Size"]; + } else { + newRet["ProjectSize"] = "1TiB"; + } // we check if it is greather than 0.4Mib if not we display with 10 // decimal points - if (newRet["Bytes"] > 400000) { - newRet["Billed"] = parseFloat(newRet["Bytes"] / 1099511627776 * 3.5) + if (newRet["Bytes"] > 900000) { + newRet["Billed"] = parseFloat(newRet["Bytes"] / 1099511627776) .toPrecision(4); } else { - newRet["Billed"] = parseFloat(newRet["Bytes"] / 1099511627776 * 3.5) + newRet["Billed"] = parseFloat(newRet["Bytes"] / 1099511627776) .toFixed(10); } return newRet; diff --git a/swift_browser_ui_frontend/src/components/ShareRequestsTable.vue b/swift_browser_ui_frontend/src/components/ShareRequestsTable.vue index 229f72876..654f3cef5 100644 --- a/swift_browser_ui_frontend/src/components/ShareRequestsTable.vue +++ b/swift_browser_ui_frontend/src/components/ShareRequestsTable.vue @@ -109,7 +109,7 @@ export default { perPage: 10, requestedSharesList: [], selected: undefined, - currentPage: 0, + currentPage: 1, defaultSortDirection: "asc", }; }, diff --git a/swift_browser_ui_frontend/src/views/Dashboard.vue b/swift_browser_ui_frontend/src/views/Dashboard.vue index 8927b4cc4..71eb7b2a3 100644 --- a/swift_browser_ui_frontend/src/views/Dashboard.vue +++ b/swift_browser_ui_frontend/src/views/Dashboard.vue @@ -32,10 +32,10 @@ {{ $t('message.dashboard.cur_billing') }}
@@ -43,7 +43,7 @@ v-else class="progress is-danger is-large" :value="Bytes" - :max="1099511627776" + :max="Bytes > 1099511627776 ? Bytes : 1099511627776" > {{ parseInt(Bytes/1099511627776) }} @@ -51,7 +51,7 @@