diff --git a/gallery/README.md b/gallery/README.md index e25eb7288..4a340b288 100644 --- a/gallery/README.md +++ b/gallery/README.md @@ -29,17 +29,19 @@ $ brew install jq Login into IBM Cloud using the CLI ``` -$ ibmcloud login +> ibmcloud login ``` Choose a proper region and resource group. In this script, we'll use the Frankfurt region and the default resource group. ``` -$ ibmcloud target -r eu-de +> REGION=eu-de + +> ibmcloud target -r $REGION ``` Create a new project ``` -$ ibmcloud ce project create --name gallery +> ibmcloud ce project create --name gallery Creating project 'gallery'... ID for project 'gallery' is '91efff97-1001-4144-997a-744ec8009303'. @@ -51,8 +53,10 @@ OK Read the project guid from the CLI output and store it in a local variable. We'll need it later on to configure the bucket. ``` -$ export CE_PROJECT_GUID=$(ibmcloud ce project current --output json|jq -r '.guid') -$ echo "CE_PROJECT_GUID: $CE_PROJECT_GUID" +> export CE_PROJECT_GUID=$(ibmcloud ce project current --output json|jq -r '.guid') +> echo "CE_PROJECT_GUID: $CE_PROJECT_GUID" + +CE_PROJECT_GUID: 91efff97-1001-4144-997a-744ec8009303 ``` Once the project has become active, you are good to proceed with the next step. @@ -61,7 +65,7 @@ Once the project has become active, you are good to proceed with the next step. Create an application ``` -$ ibmcloud ce app create --name gallery --image icr.io/codeengine/gallery +> ibmcloud ce app create --name gallery --image icr.io/codeengine/gallery Creating application 'gallery'... Configuration 'gallery' is waiting for a Revision to become ready. @@ -78,7 +82,7 @@ to compose an image gallery by either dragging the sample images into the preview box and click `Add`. Furthermore, it is possible to upload images from the local workstation, by clicking `Upload`. -Note, that the image gallery will reset, once the browser reloads. +Note, that the image gallery will reset, once the app instance tears down. ![Gallery application](./docs/application-in-memory.png) @@ -91,8 +95,8 @@ in this case IBM Cloud Object Storage. First we'll create a new instance of that service: ``` -$ ibmcloud resource service-instance-create gallery-cos \ - cloud-object-storage standard global +> ibmcloud resource service-instance-create gallery-cos \ + cloud-object-storage standard global -d premium-global-deployment-iam Creating service instance gallery-cos in resource group default of account John Doe's Account as abc@ibm.com... OK @@ -118,13 +122,13 @@ From this output you'll need to save the `ID` value for a future command. So to make life easier, let's save it as an environment variable: ``` -$ export COS_ID=$(ibmcloud resource service-instance gallery-cos --output json|jq -r '.[]|.crn') -$ echo "COS_ID: $COS_ID" +> export COS_INSTANCE_ID=$(ibmcloud resource service-instance gallery-cos --output json|jq -r '.[]|.id') +> echo "COS_INSTANCE_ID: $COS_INSTANCE_ID" ``` Let's direct all COS CLI uses to our COS instance: ``` -$ ibmcloud cos config crn --crn $COS_ID --force +> ibmcloud cos config crn --crn $COS_INSTANCE_ID --force Saving new Service Instance ID... OK @@ -135,12 +139,20 @@ Now, let's use the "IAM" authentication method which will use the same API Key that the rest of our CLI commands will use: ``` -$ ibmcloud cos config auth --method IAM +> ibmcloud cos config auth --method IAM OK Successfully switched to IAM-based authentication. The program will access your Cloud Object Storage account using your IAM Credentials. ``` +Last, let's set the region for the bucket +``` +> ibmcloud cos config region --region ${REGION} + +OK +Successfully saved default region. The program will look for buckets in the region eu-de. +``` + Next, let's go ahead and create a new bucket into which our data will be stored. To do this you'll need to provide a unique name for your bucket. It needs to be globally unique across all buckets in the IBM Cloud. In the command below @@ -148,14 +160,14 @@ we'll use our project's ID appended with "-gallery", but you can technically use easy use: ``` -$ export BUCKET="$CE_PROJECT_GUID-gallery" -$ echo "BUCKET: $BUCKET" +> export BUCKET="$CE_PROJECT_GUID-gallery" +> echo "BUCKET: $BUCKET" ``` -Now let's ask COS to create our bucket: +Now, let's ask COS to create our bucket: ``` -$ ibmcloud cos bucket-create --bucket $BUCKET +> ibmcloud cos bucket-create --bucket $BUCKET OK Details about bucket 91efff97-1001-4144-997a-744ec8009303-gallery: @@ -163,35 +175,39 @@ Region: eu-de Class: Standard ``` -To complete this setup, we'll need to adjust the application configuration and make it aware of the persistence store. +In order to enable the Code Engine app to interact with the COS bucket, we'll create a service credential that contains HMAC credentials, store it in a Code Engine secret and create a persistent data store so that Code Engine components can mount the bucket. ``` -$ ibmcloud ce app update --name gallery \ - --env BUCKET=$BUCKET \ - --scale-down-delay 3600 +> COS_HMAC_CREDENTIALS=$(ibmcloud resource service-key-create gallery-cos-credentials Writer --instance-id $COS_INSTANCE_ID --parameters '{"HMAC":true}' --output JSON) -Updating application 'gallery' to latest revision. -Traffic is not yet migrated to the latest revision. -Ingress has not yet been reconciled. -Waiting for load balancer to be ready. -Run 'ibmcloud ce application get -n gallery' to check the application status. +> ibmcloud ce secret create --name gallery-cos-hmac-credentials \ + --format hmac \ + --access-key-id "$(echo "$COS_HMAC_CREDENTIALS"|jq -r '.credentials.cos_hmac_keys.access_key_id')" \ + --secret-access-key "$(echo "$COS_HMAC_CREDENTIALS"|jq -r '.credentials.cos_hmac_keys.secret_access_key')" + +Creating hmac_auth secret 'gallery-cos-hmac-credentials'... OK -https://gallery.172utxcdky5l.eu-de.codeengine.appdomain.cloud +> ibmcloud ce persistentdatastore create --name gallery-cos-pds --cos-bucket-name $BUCKET --cos-access-secret gallery-cos-hmac-credentials + +Successfully created persistent data store named 'gallery-cos-pds'. +OK ``` -Furthermore, we'll create a service binding between the application and the COS instance, which will expose -credentials to the application allowing it to read and write to the COS instance. +To complete this setup, we'll need to adjust the application configuration and make it aware of the persistence store. ``` -$ ibmcloud ce app bind --name gallery --service-instance gallery-cos +> ibmcloud ce app update --name gallery \ + --mount-data-store /mnt/bucket=gallery-cos-pds: \ + --env MOUNT_LOCATION=/mnt/bucket \ + --scale-down-delay 3600 -Binding service instance... -Status: Done -Waiting for application revision to become ready... -The Configuration is still working to reflect the latest desired specification. +Updating application 'gallery' to latest revision. Traffic is not yet migrated to the latest revision. Ingress has not yet been reconciled. Waiting for load balancer to be ready. +Run 'ibmcloud ce application get -n gallery' to check the application status. OK + +https://gallery.172utxcdky5l.eu-de.codeengine.appdomain.cloud ``` Open the gallery application in your browser. Notice the Gallery title on the right-hand side has slightly changed. It now says `My Gallery hosted on IBM Cloud Object Storage`. Play around with the gallery by adding a few images. Notice, that the gallery images re-appear after reloading the page. @@ -243,7 +259,7 @@ In order to allow the function to read and write to the bucket, we'll need to cr List all service credentials of the Object Storage instance: ``` -$ ibmcloud resource service-keys --instance-id $COS_ID +$ ibmcloud resource service-keys --instance-id $COS_INSTANCE_ID Retrieving all service keys in resource group default under account John Does's Account as abc@ibm.com... OK @@ -253,7 +269,7 @@ gallery-ce-service-binding-prw1t active Fri Sep 8 07:56:19 UTC 2023 Extract the name of the service access secret, that has been created for the app ``` -$ export COS_SERVICE_CREDENTIAL=$(ibmcloud resource service-keys --instance-id $COS_ID --output json|jq -r '.[0].name') +$ export COS_SERVICE_CREDENTIAL=$(ibmcloud resource service-keys --instance-id $COS_INSTANCE_ID --output json|jq -r '.[0].name') $ echo "COS_SERVICE_CREDENTIAL: $COS_SERVICE_CREDENTIAL" ``` diff --git a/gallery/app/Dockerfile b/gallery/app/Dockerfile index bf5eb0a76..612718a69 100644 --- a/gallery/app/Dockerfile +++ b/gallery/app/Dockerfile @@ -1,4 +1,4 @@ -FROM registry.access.redhat.com/ubi9/nodejs-20:latest AS build-env +FROM registry.access.redhat.com/ubi10/nodejs-22-minimal:latest AS build-env WORKDIR /app # Define which files should be copied into the container image @@ -6,12 +6,13 @@ COPY *.js . COPY page.* . COPY pictures/* pictures/ COPY package.json . +RUN mkdir tmp/ # Load all dependencies RUN npm install # Use a small distroless image for as runtime image -FROM gcr.io/distroless/nodejs20-debian12 +FROM gcr.io/distroless/nodejs22-debian12 COPY --from=build-env /app /app WORKDIR /app EXPOSE 8080 diff --git a/gallery/app/app.js b/gallery/app/app.js index 94b669f92..5bc5dda4f 100644 --- a/gallery/app/app.js +++ b/gallery/app/app.js @@ -1,36 +1,12 @@ "use strict"; const { createServer } = require("http"); -const { readFileSync } = require("fs"); -const { CosService } = require("./cos-service"); +const { existsSync } = require("fs"); +const { open, readFile, writeFile, readdir, unlink } = require("fs/promises"); const basePath = __dirname; // serving files from here -const getCosConfig = () => { - const endpoint = - process.env.CLOUD_OBJECT_STORAGE_ENDPOINT || - "s3.eu-de.cloud-object-storage.appdomain.cloud"; - const serviceInstanceId = - process.env.CLOUD_OBJECT_STORAGE_RESOURCE_INSTANCE_ID; - const apiKeyId = process.env.CLOUD_OBJECT_STORAGE_APIKEY; - console.log( - `getCosConfig - endpoint: '${endpoint}', serviceInstanceId: ${serviceInstanceId}, apiKeyId: '${ - apiKeyId && "*****" - }'` - ); - - return { - endpoint, - apiKeyId, - serviceInstanceId, - }; -}; - -// Init COS -let cosService; -if (process.env.CLOUD_OBJECT_STORAGE_APIKEY) { - cosService = new CosService(getCosConfig()); -} +const GALLERY_PATH = process.env.MOUNT_LOCATION || "/app/tmp"; function getFunctionEndpoint() { if (!process.env.COLORIZER) { @@ -62,7 +38,7 @@ const mimetypeByExtension = Object.assign(Object.create(null), { png: "image/png", }); -function handleHttpReq(req, res) { +async function handleHttpReq(req, res) { let reqPath = req.url; if (reqPath.startsWith("//")) { reqPath = reqPath.slice(1); @@ -71,7 +47,7 @@ function handleHttpReq(req, res) { if (reqPath === "/") { let pageContent; try { - pageContent = readFileSync(`${basePath}/page.html`); + pageContent = await readFile(`${basePath}/page.html`); } catch (err) { console.log(`Error reading page: ${err.message}`); res.statusCode = 503; @@ -89,10 +65,9 @@ function handleHttpReq(req, res) { const enabledFeatures = {}; - if (process.env.BUCKET && process.env.CLOUD_OBJECT_STORAGE_APIKEY) { - enabledFeatures.cos = { - bucket: process.env.BUCKET, - interval: parseInt(process.env.CHECK_INTERVAL) || 1_000, + if (existsSync(GALLERY_PATH)) { + enabledFeatures.fs = { + cos: !!process.env.MOUNT_LOCATION, }; } if (process.env.COLORIZER) { @@ -124,27 +99,21 @@ function handleHttpReq(req, res) { invokeColorizeFunction(payload.imageId) .then((response) => { - console.log( - `Colorizer function has been invoked successfully: '${JSON.stringify( - response - )}'` - ); + console.log(`Colorizer function has been invoked successfully: '${JSON.stringify(response)}'`); res.statusCode = 200; res.end(); }) .catch((reason) => { console.error(`Error colorizing image '${payload.imageId}'`, reason); res.statusCode = 503; - res.end( - `Error changing color of image '${payload.imageId}': ${reason}` - ); + res.end(`Error changing color of image '${payload.imageId}': ${reason}`); return; }); }); return; } - // This handler takes care of uploading the input payload to a COS bucket + // This handler takes care of uploading the input payload to a local file system location if (reqPath === "/upload") { console.info("Uploading to COS ..."); req.on("error", (err) => { @@ -157,82 +126,65 @@ function handleHttpReq(req, res) { req.on("data", (chunkBuf) => { bodyBuf = Buffer.concat([bodyBuf, chunkBuf]); }); - req.on("end", () => { - console.log( - `received ${bodyBuf.toString("base64").length} chars as input data` - ); - cosService - .createObject( - process.env.BUCKET, - `gallery-pic-${Date.now()}.png`, - bodyBuf, - "image/png" - ) - .then((thumbnail) => { - res.setHeader("Content-Type", "application/json"); - res.statusCode = 200; - res.end(`{"done": "true"}`); - }) - .catch((reason) => { - console.log(`Error uploading picture: ${reason}`); - res.statusCode = 503; - res.end(`Error uploading picture: ${reason}`); - return; - }); - }); - return; - } - - // Handler for listing all items in the bucket - if (reqPath === "/list-bucket-content") { - // console.info("List from COS ..."); - - cosService - .getBucketContents(process.env.BUCKET, "") - .then((bucketContents) => { + req.on("end", async () => { + console.log(`received ${bodyBuf.toString("base64").length} chars as input data`); + try { + await writeFile(`${GALLERY_PATH}/gallery-pic-${Date.now()}.png`, bodyBuf, {}); res.setHeader("Content-Type", "application/json"); res.statusCode = 200; - res.end(JSON.stringify(bucketContents)); - }) - .catch((reason) => { - console.log(`Error listing bucket content: ${reason}`); + res.end(`{"done": "true"}`); + return; + } catch (err) { + console.log(`Error uploading picture: ${err}`); res.statusCode = 503; - res.end(`Error listing bucket content: ${reason}`); + res.end(`Error uploading picture: ${err}`); return; + } + }); + return; + } + + // Handler for listing all items in the gallery + if (reqPath === "/list-gallery-content") { + try { + const filenames = await readdir(GALLERY_PATH); + const galleryContents = filenames.map((file) => { + // will also include directory names + console.log(file); + return { + Key: file, + LastModified: Date.now(), + }; }); + + res.setHeader("Content-Type", "application/json"); + res.statusCode = 200; + res.end(JSON.stringify(galleryContents)); + } catch (err) { + console.log(`Error listing gallery content: ${err}`); + res.statusCode = 503; + res.end(`Error listing gallery content: ${err}`); + } return; } - // Handler for deleting all items in the bucket - if (reqPath === "/delete-bucket-content") { - console.info("Delete entire bucket content ..."); + // Handler for deleting all items in the gallery + if (reqPath === "/delete-gallery-content") { + console.info("Delete entire gallery content ..."); - cosService - .getBucketContents(process.env.BUCKET, "") - .then((bucketContents) => { - return bucketContents.map((obj) => { - return { - Key: obj.Key, - }; - }); - }) - .then((toBeDeletedObjects) => { - return cosService.deleteBucketObjects( - process.env.BUCKET, - toBeDeletedObjects - ); - }) - .then(() => { - res.setHeader("Content-Type", "application/json"); - res.statusCode = 200; - res.end(`{"done": "true"}`); - }) - .catch((reason) => { - console.log(`Error deleting bucket content: ${reason}`); - res.statusCode = 503; - res.end(`Error deleting bucket content: ${reason}`); - return; - }); + try { + for (const file of await readdir(GALLERY_PATH)) { + await unlink(`${GALLERY_PATH}/${file}`); + } + res.setHeader("Content-Type", "application/json"); + res.statusCode = 200; + res.end(`{"done": "true"}`); + } catch (err) { + console.log(`Error deleting gallery content: ${err}`); + res.statusCode = 503; + res.end(`Error deleting gallery content: ${err}`); + + } return; } @@ -241,14 +193,15 @@ function handleHttpReq(req, res) { if (pictureId.indexOf("?") > -1) { pictureId = pictureId.substring(0, pictureId.indexOf("?")); } - console.info(`Render image from COS '${pictureId}' ...`); + console.info(`Render image from gallery '${pictureId}' ...`); try { - return cosService.streamObject(process.env.BUCKET, pictureId, res); + const fd = await open(`${GALLERY_PATH}/${pictureId}`); + return fd.createReadStream().pipe(res); } catch (err) { - console.error(`Error streaming bucket content '${pictureId}'`, err); + console.error(`Error streaming gallery content '${pictureId}'`, err); res.statusCode = 503; - res.end(`Error streaming bucket content: ${err}`); + res.end(`Error streaming gallery content: ${err}`); return; } } @@ -266,17 +219,14 @@ function handleHttpReq(req, res) { // serve file at basePath/reqPath let pageContent; try { - pageContent = readFileSync(`${basePath}/${reqPath}`); + pageContent = await readFile(`${basePath}/${reqPath}`); } catch (err) { console.log(`Error reading file: ${err.message}`); res.statusCode = 404; res.end(`Error reading file: ${err.message}`); return; } - res.setHeader( - "Content-Type", - mimetypeByExtension[(reqPath.match(/\.([^.]+)$/) || [])[1]] || "text/plain" - ); + res.setHeader("Content-Type", mimetypeByExtension[(reqPath.match(/\.([^.]+)$/) || [])[1]] || "text/plain"); res.statusCode = 200; res.end(pageContent); } @@ -286,9 +236,9 @@ server.listen(8080, () => { console.log(`Listening on port 8080`); }); -process.on('SIGTERM', () => { - console.info('SIGTERM signal received.'); +process.on("SIGTERM", () => { + console.info("SIGTERM signal received."); server.close(() => { - console.log('Http server closed.'); + console.log("Http server closed."); }); -}); \ No newline at end of file +}); diff --git a/gallery/app/cos-service.js b/gallery/app/cos-service.js deleted file mode 100644 index 07353a1b5..000000000 --- a/gallery/app/cos-service.js +++ /dev/null @@ -1,167 +0,0 @@ -/******************************************************************************* - * Licensed Materials - Property of IBM - * IBM Cloud Code Engine, 5900-AB0 - * © Copyright IBM Corp. 2020, 2022 - * US Government Users Restricted Rights - Use, duplication or - * disclosure restricted by GSA ADP Schedule Contract with IBM Corp. - ******************************************************************************/ - -const ibm = require("ibm-cos-sdk"); - -class CosService { - cos; - config; - - constructor(config) { - const fn = "constructor"; - this.config = config; - this.cos = new ibm.S3(config); - console.debug( - `${fn}- initialized! instance: '${config.serviceInstanceId}'` - ); - } - - getServiceInstanceId() { - return this.config.serviceInstanceId; - } - - getContentTypeFromFileName(fileName) { - if (fileName.endsWith(".png")) { - return "image/png"; - } - if (fileName.endsWith(".jpg")) { - return "image/jpeg"; - } - if (fileName.endsWith(".xml")) { - return "application/xml"; - } - if (fileName.endsWith(".json")) { - return "application/json"; - } - if (fileName.endsWith(".html")) { - return "text/html"; - } - if (fileName.endsWith(".log")) { - return "text/plain"; - } - if (fileName.endsWith(".css")) { - return "text/css"; - } - if (fileName.endsWith(".js")) { - return "text/javascript"; - } - if (fileName.endsWith(".svg")) { - return "image/svg+xml"; - } - - return "text/plain"; - } - - /** - * https://ibm.github.io/ibm-cos-sdk-js/AWS/S3.html#putObject-property - */ - createObject(bucket, id, dataToUpload, mimeType, contentLength) { - const fn = "createObject "; - console.debug( - `${fn}> id: '${id}', mimeType: '${mimeType}', contentLength: '${contentLength}'` - ); - - return this.cos - .putObject({ - Bucket: bucket, - Key: id, - Body: dataToUpload, - ContentType: mimeType, - ContentLength: contentLength, - }) - .promise() - .then((obj) => { - console.debug(`${fn}< done`); - return true; - }) - .catch((err) => { - console.error(err); - console.debug(`${fn}< failed`); - throw err; - }); - } - - /** - * https://cloud.ibm.com/docs/cloud-object-storage?topic=cloud-object-storage-node#node-examples-list-objects - */ - getBucketContents(bucketName, prefix) { - const fn = "getBucketContents "; - //console.debug(`${fn}> bucket: '${bucketName}', prefix: '${prefix}'`); - return this.cos - .listObjects({ Bucket: bucketName, Prefix: prefix }) - .promise() - .then((data) => { - //console.debug(`${fn}< done`); - if (data != null && data.Contents != null) { - return data.Contents; - } - }) - .catch((err) => { - console.error(err); - console.debug(`${fn}< failed`); - return undefined; - }); - } - - /** - * https://ibm.github.io/ibm-cos-sdk-js/AWS/S3.html#getObject-property - * @param id - */ - getObjectAsStream(bucket, id) { - const fn = "getObjectAsStream "; - console.debug(`${fn}> id: '${id}'`); - - return this.cos.getObject({ Bucket: bucket, Key: id }).createReadStream(); - } - - - streamObject(bucket, id, res) { - const fn = "streamObject "; - console.debug(`${fn}> id: '${id}'`); - - this.cos - .getObject({ Bucket: bucket, Key: id }) - .createReadStream() - .pipe(res); - } - - /** - * https://ibm.github.io/ibm-cos-sdk-js/AWS/S3.html#deleteObjects-property - * @param {*} bucket - * @param {*} contents - * @returns - */ - deleteBucketObjects(bucketName, objects) { - const fn = "deleteBucketObjects "; - console.debug( - `${fn}> bucket: '${bucketName}', '${objects.length}' objects to delete` - ); - return this.cos - .deleteObjects({ - Bucket: bucketName, - Delete: { - Objects: objects, - Quiet: true, - }, - }) - .promise() - .then((data) => { - console.debug(`${fn}< done`); - return true; - }) - .catch((err) => { - console.error(err); - console.debug(`${fn}< failed`); - return undefined; - }); - } -} - -module.exports = { - CosService, -}; diff --git a/gallery/app/package.json b/gallery/app/package.json index 244ee9401..a40e1f3f7 100644 --- a/gallery/app/package.json +++ b/gallery/app/package.json @@ -9,6 +9,6 @@ "author": "", "license": "MIT", "dependencies": { - "ibm-cos-sdk": "^1.13.1" + } } diff --git a/gallery/app/page.css b/gallery/app/page.css index 08c82a38a..59dfe72d7 100644 --- a/gallery/app/page.css +++ b/gallery/app/page.css @@ -156,7 +156,7 @@ button:disabled { justify-content: flex-start; overflow: scroll; border: 4px solid #555; - margin: 1.5rem 0 1rem; + margin: 2.5rem 0 1rem; overflow-x: hidden; overflow-y: hidden; } @@ -166,7 +166,7 @@ button:disabled { min-height: 10rem; border: 0.25rem dotted #505050; background-color: #fff; - margin: 1.5rem 0 2rem; + margin: 2.5rem 0 2rem; } .picture-preview.highlight { diff --git a/gallery/app/page.js b/gallery/app/page.js index 6d09bc143..bb2d19394 100644 --- a/gallery/app/page.js +++ b/gallery/app/page.js @@ -1,11 +1,4 @@ -const ne = [ - "file_input", - "add", - "picture_preview", - "progress", - "upload", - "clear", -].reduce((p, c) => { +const ne = ["file_input", "add", "picture_preview", "progress", "upload", "clear"].reduce((p, c) => { p[c] = document.querySelector(`[data-${c.replace("_", "-")}]`); return p; }, Object.create(null)); @@ -19,12 +12,12 @@ ne.clear.addEventListener("click", clearGallery, false); async function clearGallery() { ne.progress.innerText = "Deleting..."; ne.add.disabled = true; - if (enabledFeatures.cos) { - // delete items from the COS bucket - await fetch("/delete-bucket-content", { method: "POST" }); + if (enabledFeatures.fs) { + // delete items from the file system location + await fetch("/delete-gallery-content", { method: "POST" }); - // reload the bucket - listBucketContent(); + // reload the gallery + listGalleryContent(); } else { // just delete the images from the HTML DOM const gallery = document.getElementById("gallery"); @@ -61,7 +54,7 @@ function uploadImg() { ne.progress.innerHTML = ""; // reload the bucket - listBucketContent(); + listGalleryContent(); } else if (xhr.readyState == 4) { ne.progress.innerHTML = xhr.responseText; } @@ -159,7 +152,7 @@ ne.picture_preview.addEventListener("drop", unhighlight, false); ne.picture_preview.addEventListener("drop", doDrop, false); -function listBucketContent() { +function listGalleryContent() { var xmlHttp = new XMLHttpRequest(); xmlHttp.onreadystatechange = function () { if (xmlHttp.readyState == 4 && xmlHttp.status == 200) { @@ -244,7 +237,7 @@ function listBucketContent() { } } }; - xmlHttp.open("GET", "./list-bucket-content", true); + xmlHttp.open("GET", "./list-gallery-content", true); xmlHttp.send(null); } @@ -254,10 +247,9 @@ const checkFeatures = async () => { enabledFeatures = await response.json(); console.info(`Available features: ${JSON.stringify(enabledFeatures)}`); - if (enabledFeatures.cos) { + if (enabledFeatures.fs) { // list all images that are stored in the bucket - listBucketContent(); - //setInterval(listBucketContent, enabledFeatures.cos.interval); + listGalleryContent(); // enable the handler to upload images to COS ne.add.addEventListener("click", uploadImg, false); @@ -266,12 +258,13 @@ const checkFeatures = async () => { var refreshBtn = document.createElement("button"); refreshBtn.className = "cta-refresh"; refreshBtn.innerText = "Refresh"; - refreshBtn.addEventListener("click", listBucketContent, false); + refreshBtn.addEventListener("click", listGalleryContent, false); document.getElementById("gallery-cta-buttons").appendChild(refreshBtn); - document.getElementById("gallery-title-text").innerText = - "My Gallery hosted on IBM Cloud Object Storage"; + if (enabledFeatures.fs.cos) { + document.getElementById("gallery-title-text").innerText = "My Gallery hosted on IBM Cloud Object Storage"; + } } else { // fallback to store image on the client side ne.add.addEventListener("click", addImg, false);