cd0b41f1116bd93328ab34e9eceaa5f9afa8b825 chmalee Thu Feb 6 13:36:29 2025 -0800 Lots of changes to hub space UI after code review and QA feedback. 1) Make the table interface more like a file explorer where you view the 'root' directory by default and then can click into 'folders'. Also adds a breadcrumb nav so you can go backwards. 2) Fix bug in delete command to remove directories correctly. 3) cgiEncode the parentDir of incoming requests to prevent moving files to weird locations diff --git src/hg/hgHubConnect/trackHubWizard.c src/hg/hgHubConnect/trackHubWizard.c index a21592ef726..500677e76f4 100644 --- src/hg/hgHubConnect/trackHubWizard.c +++ src/hg/hgHubConnect/trackHubWizard.c @@ -1,208 +1,216 @@ /* trackHubWizard -- a user interface for creating a track hubs configuration files */ /* Copyright (C) 2019 The Regents of the University of California * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */ #include "common.h" #include "cart.h" #include "cheapcgi.h" #include "hdb.h" #include "hgConfig.h" #include "md5.h" #include "trashDir.h" #include "hgHubConnect.h" #include "jsHelper.h" #include "web.h" #include "wikiLink.h" #include "customTrack.h" #include "userdata.h" #include "jsonWrite.h" #include "cartJson.h" #include "hubSpace.h" #include "hubSpaceKeys.h" #include "hubConnect.h" #include "trackHub.h" #include "htmshell.h" void removeOneFile(char *userName, char *cgiFileName, char *fullPath, char *db, char *fileType) /* Remove one single file for userName */ { // prefixUserFile returns a canonicalized path, or NULL if the // canonicalized path does not begin with the hg.conf specified userDataDir // TODO: make the debug information from stderr go to stdout so the user // can know there is a mistake somewhere, and only print the debug // information in the event that the filename actually begins with the // userDataDir so we don't tell hackers what files do and do not exist char *fileName = prefixUserFile(userName, fullPath, NULL); if (fileName) { if (fileExists(fileName)) { fprintf(stderr, "deleting file: '%s'\n", fileName); removeFileForUser(fileName, userName); fflush(stderr); } else { fprintf(stderr, "file '%s' does not exist\n", fileName); fflush(stderr); } } } void doRemoveFile(struct cartJson *cj, struct hash *paramHash) /* Process the request to remove a file */ { char *userName = getUserName(); if (userName) { // our array of objects, each object represents a track file struct jsonElement *deleteJson = hashFindVal(paramHash, "fileList"); struct slRef *copy, *f, *fileList = deleteJson->val.jeList; struct slRef *dirList = NULL; jsonWriteListStart(cj->jw, "deletedList"); for (f = fileList; f != NULL; ) { struct jsonElement *fileObj = (struct jsonElement *)f->val; char *fileName = jsonStringField(fileObj, "fileName"); char *fileType = jsonStringField(fileObj, "fileType"); char *db = jsonStringField(fileObj, "genome"); char *fullPath = jsonStringField(fileObj, "fullPath"); copy = f->next; if (sameString(fileType, "dir")) { f->next = NULL; slAddHead(&dirList, f); } else { removeOneFile(userName, fileName, fullPath, db, fileType); // write out the fullPath so the DataTable can remove the correct row: jsonWriteString(cj->jw, NULL, fullPath); } f = copy; } // now attempt to delete any requested directories, but don't die if they still have contents for (f = dirList; f != NULL; f = f->next) { struct jsonElement *fileObj = (struct jsonElement *)f->val; char *fileName = jsonStringField(fileObj, "fileName"); char *fileType = jsonStringField(fileObj, "fileType"); char *db = jsonStringField(fileObj, "genome"); char *fullPath = jsonStringField(fileObj, "fullPath"); removeOneFile(userName, fileName, fullPath, db, fileType); // write out the fullPath so the DataTable can remove the correct row: jsonWriteString(cj->jw, NULL, fullPath); } jsonWriteListEnd(cj->jw); } } void doMoveFile(struct cartJson *cj, struct hash *paramHash) /* Move a file to a new hub */ { } -static void outFilesForUser() -/* List out the currently stored files for the user and their sizes */ +static void outUiDataForUser(struct jsonWrite *jw) +/* List out the currently stored files for the user as well as other data + * needed to create the hubSpace table */ { char *userName = getUserName(); -struct jsonWrite *jw = jsonWriteNew(); // the JSON to return for the client javascript -jsonWriteObjectStart(jw, NULL); +jsonWriteObjectStart(jw, "userFiles"); if (userName) { // the url for this user: jsonWriteString(jw, "userUrl", webDataDir(userName)); jsonWriteListStart(jw, "fileList"); struct hubSpace *file, *fileList = listFilesForUser(userName); for (file = fileList; file != NULL; file = file->next) { jsonWriteObjectStart(jw, NULL); cgiDecodeFull(file->fileName, file->fileName, strlen(file->fileName)); jsonWriteString(jw, "fileName", file->fileName); jsonWriteNumber(jw, "fileSize", file->fileSize); jsonWriteString(jw, "fileType", file->fileType); jsonWriteString(jw, "parentDir", file->parentDir); jsonWriteString(jw, "genome", file->db); jsonWriteString(jw, "lastModified", file->lastModified); jsonWriteString(jw, "uploadTime", file->creationTime); jsonWriteString(jw, "fullPath", stripDataDir(file->location, userName)); jsonWriteString(jw, "md5sum", file->md5sum); jsonWriteObjectEnd(jw); } jsonWriteListEnd(jw); } -jsonWriteObjectEnd(jw); -jsInlineF("var isLoggedIn = %s\n", getUserName() ? "true" : "false"); -jsInlineF("var userFiles = %s;\n", dyStringCannibalize(&jw->dy)); -jsInlineF("var hubNameDefault = \"%s\";\n", defaultHubNameForUser(getUserName())); +jsonWriteBoolean(jw, "isLoggedIn", getUserName() ? TRUE : FALSE); +jsonWriteString(jw, "hubNameDefault", defaultHubNameForUser(getUserName())); // if the user is not logged, the 0 for the quota is ignored -jsInlineF("var userQuota = %llu\n", getUserName() ? checkUserQuota(getUserName()) : 0); -jsInlineF("var maxQuota = %llu\n", getUserName() ? getMaxUserQuota(getUserName()) : HUB_SPACE_DEFAULT_QUOTA); -jsonWriteFree(&jw); +jsonWriteNumber(jw, "userQuota", getUserName() ? checkUserQuota(getUserName()) : 0); +jsonWriteNumber(jw, "maxQuota", getUserName() ? getMaxUserQuota(getUserName()) : HUB_SPACE_DEFAULT_QUOTA); +jsonWriteObjectEnd(jw); +} + +void getHubSpaceUIState(struct cartJson *cj, struct hash *paramHash) +/* Get all the data we need to make a users hubSpace UI table. The cartJson library + * deals with printing the json */ +{ +outUiDataForUser(cj->jw); } void doTrackHubWizard(char *database) /* Offer an upload form so users can upload all their hub files */ { jsIncludeFile("utils.js", NULL); jsIncludeFile("ajax.js", NULL); jsIncludeFile("lodash.3.10.0.compat.min.js", NULL); jsIncludeFile("cart.js", NULL); puts("\n"); puts("\n"); puts(""); puts("\n"); puts(""); puts("\n"); puts(""); puts(""); puts(""); jsIncludeFile("hgMyData.js", NULL); // the skeleton HTML: webIncludeFile("inc/hgMyData.html"); webIncludeResourceFile("hgMyData.css"); -// get the current files stored for this user -outFilesForUser(); +// get the current files and vars stored for this user +struct jsonWrite *jw = jsonWriteNew(); +outUiDataForUser(jw); +jsInlineF("\nvar uiData = {%s}\n", jw->dy->string); +jsonWriteFree(&jw); jsInlineF("\nvar cartDb=\"%s %s\";\n", trackHubSkipHubName(hGenome(database)), database); jsInlineF("\nvar tusdEndpoint=\"%s\";\n", cfgOptionDefault("hubSpaceTusdEndpoint", NULL)); jsInline("$(document).ready(function() {\nhubCreate.init();\n})"); puts(""); } void revokeApiKey(struct cartJson *cj, struct hash *paramHash) /* Remove any api keys for the user */ { char *userName = getUserName(); struct sqlConnection *conn = hConnectCentral(); struct dyString *query = sqlDyStringCreate("delete from %s where userName='%s'", HUBSPACE_AUTH_TABLE, userName); sqlUpdate(conn, dyStringCannibalize(&query)); hDisconnectCentral(&conn); jsonWriteString(cj->jw, "revoke", "true"); } void generateApiKey(struct cartJson *cj, struct hash *paramHash) /* Make a random (but not crypto-secure api key for use of hubtools to upload to hubspace */ { char *userName = getUserName(); if (!userName) { jsonWriteString(cj->jw, "error", "generateApiKey: not logged in"); return; } char *apiKey = makeRandomKey(256); // just needs some arbitrary length // save this key to the database for this user, the 'on duplicate' part automatically revokes old keys struct sqlConnection *conn = hConnectCentral(); struct dyString *query = sqlDyStringCreate("insert into %s values ('%s', '%s') on duplicate key update apiKey='%s'", HUBSPACE_AUTH_TABLE, userName, apiKey, apiKey); sqlUpdate(conn, dyStringCannibalize(&query)); jsonWriteString(cj->jw, "apiKey", apiKey); hDisconnectCentral(&conn); }