20846: Merge branch '19213-ubuntu2204-support' into 20846-ubuntu2204
[arvados.git] / apps / workbench / app / assets / javascripts / components / search.js
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 window.SearchResultsTable = {
6     maybeLoadMore: function(dom) {
7         var loader = this.loader
8         if (loader.state != loader.READY)
9             // Can't start getting more items anyway: no point in
10             // checking anything else.
11             return
12         var contentRect = dom.getBoundingClientRect()
13         var scroller = window // TODO: use dom's nearest ancestor with scrollbars
14         if (contentRect.bottom < 2 * scroller.innerHeight) {
15             // We have less than 1 page worth of content available
16             // below the visible area. Load more.
17             loader.loadMore()
18             // Indicate loading is in progress.
19             window.requestAnimationFrame(m.redraw)
20         }
21     },
22     oncreate: function(vnode) {
23         vnode.state.maybeLoadMore = vnode.state.maybeLoadMore.bind(vnode.state, vnode.dom)
24         window.addEventListener('scroll', vnode.state.maybeLoadMore)
25         window.addEventListener('resize', vnode.state.maybeLoadMore)
26         vnode.state.timer = window.setInterval(vnode.state.maybeLoadMore, 200)
27         vnode.state.loader = vnode.attrs.loader
28         vnode.state.onupdate(vnode)
29     },
30     onupdate: function(vnode) {
31         vnode.state.loader = vnode.attrs.loader
32     },
33     onremove: function(vnode) {
34         window.clearInterval(vnode.state.timer)
35         window.removeEventListener('scroll', vnode.state.maybeLoadMore)
36         window.removeEventListener('resize', vnode.state.maybeLoadMore)
37     },
38     view: function(vnode) {
39         var loader = vnode.attrs.loader
40         var iconsMap = {
41             collections: m('i.fa.fa-fw.fa-archive'),
42             projects: m('i.fa.fa-fw.fa-folder'),
43         }
44         var db = new SessionDB()
45         var sessions = db.loadActive()
46         return m('table.table.table-condensed', [
47             m('thead', m('tr', [
48                 m('th'),
49                 m('th', 'uuid'),
50                 m('th', 'name'),
51                 m('th', 'last modified'),
52             ])),
53             m('tbody', [
54                 loader.items().map(function(item) {
55                     var session = sessions[item.uuid.slice(0,5)]
56                     var tokenParam = ''
57                     // Add the salted token to search result links from federated
58                     // remote hosts.
59                     if (!session.isFromRails && session.token.indexOf('v2/') == 0) {
60                         tokenParam = session.token
61                     }
62                     return m('tr', [
63                         m('td', m('form', {
64                             action: item.workbenchBaseURL() + '/' + item.objectType.wb_path + '/' + item.uuid,
65                             method: 'GET'
66                         }, [
67                             tokenParam !== '' &&
68                                 m('input[type=hidden][name=api_token]', {value: tokenParam}),
69                             item.workbenchBaseURL() &&
70                                 m('button.btn.btn-xs.btn-default[type=submit]', {
71                                     'data-original-title': 'show '+item.objectType.description,
72                                     'data-placement': 'top',
73                                     'data-toggle': 'tooltip',
74                                     // Bootstrap's tooltip feature
75                                     oncreate: function(vnode) { $(vnode.dom).tooltip() },
76                                 }, iconsMap[item.objectType.wb_path]),
77                         ])),
78                         m('td.arvados-uuid', item.uuid),
79                         m('td', item.name || '(unnamed)'),
80                         m('td', m(LocalizedDateTime, {parse: item.modified_at})),
81                     ])
82                 }),
83             ]),
84             loader.state == loader.DONE ? null : m('tfoot', m('tr', [
85                 m('th[colspan=4]', m('button.btn.btn-xs', {
86                     className: loader.state == loader.LOADING ? 'btn-default' : 'btn-primary',
87                     style: {
88                         display: 'block',
89                         width: '12em',
90                         marginLeft: 'auto',
91                         marginRight: 'auto',
92                     },
93                     disabled: loader.state == loader.LOADING,
94                     onclick: function() {
95                         loader.loadMore()
96                         return false
97                     },
98                 }, loader.state == loader.LOADING ? '(loading)' : 'Load more')),
99             ])),
100         ])
101     },
102 }
103
104 window.Search = {
105     oninit: function(vnode) {
106         vnode.state.sessionDB = new SessionDB()
107         vnode.state.sessionDB.autoRedirectToHomeCluster('/search')
108         vnode.state.searchEntered = m.stream()
109         vnode.state.searchActive = m.stream()
110         // When searchActive changes (e.g., when restoring state
111         // after navigation), update the text field too.
112         vnode.state.searchActive.map(vnode.state.searchEntered)
113         // When searchActive changes, create a new loader that filters
114         // with the given search term.
115         vnode.state.searchActive.map(function(q) {
116             var sessions = vnode.state.sessionDB.loadActive()
117             vnode.state.loader = new MergingLoader({
118                 children: Object.keys(sessions).map(function(key) {
119                     var session = sessions[key]
120                     var workbenchBaseURL = function() {
121                         return vnode.state.sessionDB.workbenchBaseURL(session)
122                     }
123                     var searchable_objects = [
124                         {
125                             wb_path: 'projects',
126                             api_path: 'arvados/v1/groups',
127                             filters: [['group_class', '=', 'project']],
128                             description: 'project',
129                         },
130                         {
131                             wb_path: 'projects',
132                             api_path: 'arvados/v1/groups',
133                             filters: [['group_class', '=', 'filter']],
134                             description: 'project',
135                         },
136                         {
137                             wb_path: 'collections',
138                             api_path: 'arvados/v1/collections',
139                             filters: [],
140                             description: 'collection',
141                         },
142                     ]
143                     return new MergingLoader({
144                         sessionKey: key,
145                         // For every session, search for every object type
146                         children: searchable_objects.map(function(obj_type) {
147                             return new MultipageLoader({
148                                 sessionKey: key,
149                                 loadFunc: function(filters) {
150                                     // Apply additional type dependant filters
151                                     filters = filters.concat(obj_type.filters).concat(ilike_filters(q))
152                                     return vnode.state.sessionDB.request(session, obj_type.api_path, {
153                                         data: {
154                                             filters: JSON.stringify(filters),
155                                             count: 'none',
156                                         },
157                                     }).then(function(resp) {
158                                         resp.items.map(function(item) {
159                                             item.workbenchBaseURL = workbenchBaseURL
160                                             item.objectType = obj_type
161                                         })
162                                         return resp
163                                     })
164                                 },
165                             })
166                         }),
167                     })
168                 }),
169             })
170         })
171     },
172     view: function(vnode) {
173         return m('form', {
174             onsubmit: function() {
175                 vnode.state.searchActive(vnode.state.searchEntered())
176                 vnode.state.forgetSavedHeight = true
177                 return false
178             },
179         }, [
180             m(SaveUIState, {
181                 defaultState: '',
182                 currentState: vnode.state.searchActive,
183                 forgetSavedHeight: vnode.state.forgetSavedHeight,
184                 saveBodyHeight: true,
185             }),
186             vnode.state.loader && [
187                 m('.row', [
188                     m('.col-md-6', [
189                         m('.input-group', [
190                             m('input#search.form-control[placeholder=Search collections and projects]', {
191                                 oninput: m.withAttr('value', vnode.state.searchEntered),
192                                 value: vnode.state.searchEntered(),
193                             }),
194                             m('.input-group-btn', [
195                                 m('input.btn.btn-primary[type=submit][value="Search"]'),
196                             ]),
197                         ]),
198                     ]),
199                     m('.col-md-6', [
200                         'Searching sites: ',
201                         vnode.state.loader.children.length == 0
202                             ? m('span.label.label-xs.label-danger', 'none')
203                             : vnode.state.loader.children.map(function(child) {
204                                 return [m('span.label.label-xs', {
205                                     className: child.state == child.LOADING ? 'label-warning' : 'label-success',
206                                 }, child.sessionKey), ' ']
207                             }),
208                         ' ',
209                         m('a[href="/sessions"]', 'Add/remove sites'),
210                     ]),
211                 ]),
212                 m(SearchResultsTable, {
213                     loader: vnode.state.loader,
214                 }),
215             ],
216         ])
217     },
218 }