diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/ApplicationStatisticsContainer.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/ApplicationStatisticsContainer.vue new file mode 100644 index 000000000..f3430432d --- /dev/null +++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/ApplicationStatisticsContainer.vue @@ -0,0 +1,213 @@ + + + \ No newline at end of file diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/ComputeResourceStatisticsContainer.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/ComputeResourceStatisticsContainer.vue new file mode 100644 index 000000000..c5c15e7e5 --- /dev/null +++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/ComputeResourceStatisticsContainer.vue @@ -0,0 +1,104 @@ + + + \ No newline at end of file diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/ExperimentStatisticsContainer.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/ExperimentStatisticsContainer.vue index db4628e8a..d9d8f07fc 100644 --- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/ExperimentStatisticsContainer.vue +++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/ExperimentStatisticsContainer.vue @@ -11,20 +11,12 @@ - + - Load + Load @@ -34,20 +26,12 @@ - + - Load + Load @@ -55,249 +39,179 @@ - - - -
-
- - - - - - - - Past 24 Hours - Past Week - - - - Username - Application Name - Hostname - - - - - - - Remove username filter - - - - - - - - - - - Remove application name filter - - - - - - - - - - - Remove hostname filter - - - - - -
-
-
-
-

- Experiment Statistics from {{ fromTimeDisplay }} to - {{ toTimeDisplay }} -

-
+ + + + + + + + + Past 24 Hours + Past Week + + + + + + + +
+
+ + + Username + Application Name + Hostname + + + + + + + Remove username filter + + + + + + + + + + + Remove application name filter + + + + + + + + + + + Remove hostname filter + + + + +
-
- - +
+
+

+ Experiment Statistics from {{ fromTimeDisplay }} to + {{ toTimeDisplay }} +

+
-
- - +
+
+ + All + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
-
- - -
-
- - -
-
- - -
-
-
-
- - - - - - - - - - +
+
+ + + + + + + + + + + +
-
- - - + + + + + + + + + +
\ No newline at end of file diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/UserStatisticsContainer.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/UserStatisticsContainer.vue new file mode 100644 index 000000000..bbc34cce0 --- /dev/null +++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/UserStatisticsContainer.vue @@ -0,0 +1,291 @@ + + + \ No newline at end of file diff --git a/django_airavata/apps/admin/yarn.lock b/django_airavata/apps/admin/yarn.lock index 7e2dbc880..4def71183 100644 --- a/django_airavata/apps/admin/yarn.lock +++ b/django_airavata/apps/admin/yarn.lock @@ -3723,6 +3723,11 @@ char-regex@^2.0.0: resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-2.0.1.tgz#6dafdb25f9d3349914079f010ba8d0e6ff9cd01e" integrity sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw== +chart.js@^3.7.0: + version "3.9.1" + resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.9.1.tgz#3abf2c775169c4c71217a107163ac708515924b8" + integrity sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w== + chokidar@^2.0.2: version "2.1.8" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" @@ -11154,6 +11159,11 @@ vue-async-computed@^3.9.0: resolved "https://registry.yarnpkg.com/vue-async-computed/-/vue-async-computed-3.9.0.tgz#af3181c25168bfe9d86d8ffbc7033bf9e484fe84" integrity sha512-ac6m/9zxHHNGGKNOU1en8qNk+fAmEbJLuWL7qyQTFuH3vjv3V4urv//QHcVzCobROM6btnaDG2b+XYMncF/ETA== +vue-chartjs@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/vue-chartjs/-/vue-chartjs-4.1.2.tgz#f899ba14f3b80660f8d2c610a015341806dfc437" + integrity sha512-QSggYjeFv/L4jFSBQpX8NzrAvX0B+Ha6nDgxkTG8tEXxYOOTwKI4phRLe+B4f+REnkmg7hgPY24R0cixZJyXBg== + vue-datetime@^1.0.0-beta.10: version "1.0.0-beta.11" resolved "https://registry.yarnpkg.com/vue-datetime/-/vue-datetime-1.0.0-beta.11.tgz#283b5182dabe0ab372e375e9c47acb3b41524ffa" diff --git a/django_airavata/apps/api/serializers.py b/django_airavata/apps/api/serializers.py index 1f5f97ae3..4617a93ad 100644 --- a/django_airavata/apps/api/serializers.py +++ b/django_airavata/apps/api/serializers.py @@ -44,6 +44,7 @@ DataReplicaLocationModel ) from airavata.model.experiment.ttypes import ( + CpuUsage, ExperimentModel, ExperimentStatistics, ExperimentSummaryModel @@ -1202,6 +1203,9 @@ class ExperimentStatisticsSerializer( createdExperiments = BaseExperimentSummarySerializer(many=True) runningExperiments = BaseExperimentSummarySerializer(many=True) +class CpuUsageSerializer(thrift_utils.create_serializer_class(CpuUsage)): + pass + class UnverifiedEmailUserProfile(serializers.Serializer): userId = serializers.CharField() diff --git a/django_airavata/apps/api/static/django_airavata_api/js/index.js b/django_airavata/apps/api/static/django_airavata_api/js/index.js index 9cb8a6550..4da4019a5 100644 --- a/django_airavata/apps/api/static/django_airavata_api/js/index.js +++ b/django_airavata/apps/api/static/django_airavata_api/js/index.js @@ -118,6 +118,7 @@ const services = { ApplicationModuleService: ServiceFactory.service("ApplicationModules"), CloudJobSubmissionService, ComputeResourceService: ServiceFactory.service("ComputeResources"), + CpuUsageService: ServiceFactory.service("CpuUsages"), CredentialSummaryService: ServiceFactory.service("CredentialSummaries"), DataProductService: ServiceFactory.service("DataProducts"), ExperimentArchiveService: ServiceFactory.service("ExperimentArchive"), diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/CpuUsage.js b/django_airavata/apps/api/static/django_airavata_api/js/models/CpuUsage.js new file mode 100644 index 000000000..83663c943 --- /dev/null +++ b/django_airavata/apps/api/static/django_airavata_api/js/models/CpuUsage.js @@ -0,0 +1,14 @@ +import BaseModel from "./BaseModel"; + +const FIELDS = [ + "experimentId", + "executionId", + "userName", + "cpuHours", +]; + +export default class CpuUsage extends BaseModel { + constructor(data = {}) { + super(FIELDS, data); + } + } \ No newline at end of file diff --git a/django_airavata/apps/api/static/django_airavata_api/js/service_config.js b/django_airavata/apps/api/static/django_airavata_api/js/service_config.js index 73d134f5f..3809ea5e4 100644 --- a/django_airavata/apps/api/static/django_airavata_api/js/service_config.js +++ b/django_airavata/apps/api/static/django_airavata_api/js/service_config.js @@ -3,6 +3,7 @@ import ApplicationInterfaceDefinition from "./models/ApplicationInterfaceDefinit import ApplicationModule from "./models/ApplicationModule"; import BatchQueue from "./models/BatchQueue"; import ComputeResourceDescription from "./models/ComputeResourceDescription"; +import CpuUsage from "./models/CpuUsage"; import CredentialSummary from "./models/CredentialSummary"; import DataProduct from "./models/DataProduct"; import Experiment from "./models/Experiment"; @@ -128,6 +129,23 @@ export default { }, modelClass: ComputeResourceDescription, }, + CpuUsages: { + url: "/api/cpu-usages", + methods: { + get: { + url: "/api/cpu-usages", + requestType: "get", + queryParams: [ + "fromTime", + "toTime", + "limit", + "offset", + ], + pagination: true, + modelClass: CpuUsage, + }, + }, + }, CredentialSummaries: { url: "/api/credential-summaries/", viewSet: ["list", "retrieve", "delete"], @@ -317,6 +335,20 @@ export default { pagination: true, queryParams: ["limit", "offset"], modelClass: Group, + methods: { + getGroupsFilteredByCreationDate: { + url: "/api/groups/groups_filtered_by_creation_date", + requestType: "get", + queryParams: [ + "fromTime", + "toTime", + "limit", + "offset", + ], + pagination: true, + modelClass: Group + }, + }, }, IAMUserProfiles: { url: "/api/iam-user-profiles", diff --git a/django_airavata/apps/api/urls.py b/django_airavata/apps/api/urls.py index 474ebca1c..0b0ecc0b8 100644 --- a/django_airavata/apps/api/urls.py +++ b/django_airavata/apps/api/urls.py @@ -90,6 +90,9 @@ re_path(r'^experiment-statistics', views.ExperimentStatisticsView.as_view(), name="experiment-statistics"), + re_path(r'^cpu-usages', + views.CpuUsageView.as_view(), + name="cpu-usage"), re_path(r'ack-notifications//', views.AckNotificationViewSet.as_view(), name="ack-notifications"), re_path(r'ack-notifications/', views.AckNotificationViewSet.as_view(), diff --git a/django_airavata/apps/api/views.py b/django_airavata/apps/api/views.py index b5cc24e5d..aecb93e8c 100644 --- a/django_airavata/apps/api/views.py +++ b/django_airavata/apps/api/views.py @@ -139,6 +139,31 @@ def _send_users_added_to_group(self, internal_user_ids, group): user=user_profile, groups=[group], request=self.request) + + @action(detail=False) + def groups_filtered_by_creation_date(self, request): + view = self + + if 'fromTime' in request.GET: + from_time = view_utils.convert_utc_iso8601_to_date( + request.GET['fromTime']).timestamp() * 1000 + else: + from_time = (datetime.utcnow() - + timedelta(days=7)).timestamp() * 1000 + from_time = int(from_time) + if 'toTime' in request.GET: + to_time = view_utils.convert_utc_iso8601_to_date( + request.GET['toTime']).timestamp() * 1000 + else: + to_time = datetime.utcnow().timestamp() * 1000 + to_time = int(to_time) + limit = int(request.GET.get('limit', '-1')) + offset = int(request.GET.get('offset', '0')) + group_manager = view.request.profile_service['group_manager'] + groups = group_manager.getGroupsFilteredByCreationDate(view.authz_token, from_time, to_time) + end = offset + limit if limit > 0 else len(groups) + groups = groups[offset:end] if groups else [] + return Response(groups) class ProjectViewSet(APIBackedViewSet): @@ -1731,6 +1756,32 @@ def get(self, request, format=None): response.data['offset'] = offset return response +class CpuUsageView(APIView): + serializer_class = serializers.CpuUsageSerializer + permission_classes = (IsAuthenticated, IsInAdminsGroupPermission,) + + def get(self, request, format=None): + if 'fromTime' in request.GET: + from_time = view_utils.convert_utc_iso8601_to_date( + request.GET['fromTime']).timestamp() * 1000 + else: + from_time = (datetime.utcnow() - + timedelta(days=7)).timestamp() * 1000 + from_time = int(from_time) + if 'toTime' in request.GET: + to_time = view_utils.convert_utc_iso8601_to_date( + request.GET['toTime']).timestamp() * 1000 + else: + to_time = datetime.utcnow().timestamp() * 1000 + to_time = int(to_time) + limit = int(request.GET.get('limit', '-1')) + offset = int(request.GET.get('offset', '0')) + cpu_usages = request.airavata_client.getCpuUsages( + request.authz_token, settings.GATEWAY_ID, from_time, to_time) + end = offset + limit if limit > 0 else len(cpu_usages) + cpu_usages = cpu_usages[offset:end] if cpu_usages else [] + return Response(cpu_usages) + class UnverifiedEmailUserViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, diff --git a/django_airavata/static/common/js/components/BarChart.vue b/django_airavata/static/common/js/components/BarChart.vue new file mode 100644 index 000000000..ccd2f310e --- /dev/null +++ b/django_airavata/static/common/js/components/BarChart.vue @@ -0,0 +1,33 @@ + + + \ No newline at end of file diff --git a/django_airavata/static/common/js/components/LineChart.vue b/django_airavata/static/common/js/components/LineChart.vue new file mode 100644 index 000000000..d7880fc17 --- /dev/null +++ b/django_airavata/static/common/js/components/LineChart.vue @@ -0,0 +1,38 @@ + + + \ No newline at end of file diff --git a/django_airavata/static/common/js/components/PieChart.vue b/django_airavata/static/common/js/components/PieChart.vue new file mode 100644 index 000000000..c5b0b7e77 --- /dev/null +++ b/django_airavata/static/common/js/components/PieChart.vue @@ -0,0 +1,33 @@ + + + \ No newline at end of file diff --git a/django_airavata/static/common/js/index.js b/django_airavata/static/common/js/index.js index 68c9719dd..02d16a1c7 100644 --- a/django_airavata/static/common/js/index.js +++ b/django_airavata/static/common/js/index.js @@ -1,6 +1,7 @@ import ApplicationCard from "./components/ApplicationCard.vue"; import ApplicationName from "./components/ApplicationName"; import AutocompleteTextInput from "./components/AutocompleteTextInput.vue"; +import BarChart from "./components/BarChart.vue"; import ClipboardCopyButton from "./components/ClipboardCopyButton.vue"; import ClipboardCopyLink from "./components/ClipboardCopyLink.vue"; import ComputeResourceName from "./components/ComputeResourceName"; @@ -14,8 +15,10 @@ import FavoriteToggle from "./components/FavoriteToggle"; import GatewayGroupsBadge from "./components/GatewayGroupsBadge"; import HumanDate from "./components/HumanDate.vue"; import Linkify from "./components/Linkify.vue"; +import LineChart from "./components/LineChart.vue"; import MainLayout from "./components/MainLayout.vue"; import Pager from "./components/Pager.vue"; +import PieChart from "./components/PieChart.vue"; import ShareButton from "./components/ShareButton.vue"; import Sidebar from "./components/Sidebar.vue"; import SidebarFeed from "./components/SidebarFeed.vue"; @@ -46,6 +49,7 @@ const components = { ApplicationCard, ApplicationName, AutocompleteTextInput, + BarChart, ClipboardCopyButton, ClipboardCopyLink, ComputeResourceName, @@ -59,7 +63,9 @@ const components = { GatewayGroupsBadge, HumanDate, Linkify, + LineChart, MainLayout, + PieChart, ShareButton, Sidebar, SidebarFeed, diff --git a/django_airavata/static/common/package.json b/django_airavata/static/common/package.json index f6a8925da..952c05e69 100644 --- a/django_airavata/static/common/package.json +++ b/django_airavata/static/common/package.json @@ -25,6 +25,7 @@ "bootstrap": "^4.3.1", "bootstrap-vue": "^2.21.2", "browserslist": "^4.16.7", + "chart.js": "^3.7.0", "clipboard": "^2.0.4", "core-js": "^3.8.3", "django-airavata-api": "link:../../apps/api", @@ -34,7 +35,8 @@ "popper.js": "^1.14.6", "terser": "^4.1.2", "vue": "^2.5.22", - "vue-async-computed": "^3.9.0" + "vue-async-computed": "^3.9.0", + "vue-chartjs": "^4.1.2" }, "devDependencies": { "@babel/core": "^7.12.16", diff --git a/django_airavata/static/common/yarn.lock b/django_airavata/static/common/yarn.lock index ecc4c4f12..ed41eaa28 100644 --- a/django_airavata/static/common/yarn.lock +++ b/django_airavata/static/common/yarn.lock @@ -2553,6 +2553,11 @@ chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chart.js@^3.7.0: + version "3.9.1" + resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.9.1.tgz#3abf2c775169c4c71217a107163ac708515924b8" + integrity sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w== + "chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" @@ -7983,6 +7988,11 @@ vue-async-computed@^3.9.0: resolved "https://registry.yarnpkg.com/vue-async-computed/-/vue-async-computed-3.9.0.tgz#af3181c25168bfe9d86d8ffbc7033bf9e484fe84" integrity sha512-ac6m/9zxHHNGGKNOU1en8qNk+fAmEbJLuWL7qyQTFuH3vjv3V4urv//QHcVzCobROM6btnaDG2b+XYMncF/ETA== +vue-chartjs@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/vue-chartjs/-/vue-chartjs-4.1.2.tgz#f899ba14f3b80660f8d2c610a015341806dfc437" + integrity sha512-QSggYjeFv/L4jFSBQpX8NzrAvX0B+Ha6nDgxkTG8tEXxYOOTwKI4phRLe+B4f+REnkmg7hgPY24R0cixZJyXBg== + vue-eslint-parser@^8.0.1: version "8.3.0" resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-8.3.0.tgz#5d31129a1b3dd89c0069ca0a1c88f970c360bd0d"