Upload Files from a RobustOS Application Web Interface

Upload files from a RobustOS application Web page

Extend a generated RobustOS application page with an upload control, include the customized page in the RPK, and preserve a test upload across a device reboot. This workflow uses the sample_uci_upload example and keeps the browser upload, application restart and persistent copy as separate steps.

Applies to: A matching RobustOS SDK and development device that provide the sample_uci_upload integration, its generated Web interface helpers and the /action/import_file endpoint. Custom page JavaScript is tied to the firmware's Web interface implementation. Check it again when the firmware changes.

You need an installed baseline application, the corresponding package source, authorized root shell access to the development device, and an authenticated browser session. Complete configuration and status integration first. Use a harmless test JSON file; transfer success does not establish that an application's configuration file is valid or safe to consume.

Understand the path from browser to application

Stage Location and result
Page customization The generated configuration JavaScript in /www/web/js displays a file input and Import button.
Upload The authenticated page posts a file to /action/import_file; this example names the device-side file sample_uci.json.
Temporary storage The uploaded file is available at /tmp/upload/sample_uci.json. It is lost at reboot unless copied elsewhere first.
Application startup The lifecycle script restores the custom JavaScript and copies an available upload to /nvm/json.
Application consumption Your application reads and validates the persistent file using its own format and import logic. That logic is not implemented by the upload control.

A successful HTTP response is only the upload stage. Check the actual device file, the later persistent copy and the application's result separately.

Locate and preserve the generated page

On the development device, inspect /www/web/js. Generated page filenames follow group_menu_tab.js, with uppercase letters changed to lowercase and spaces changed to underscores.

For the example's Services > SDK Sample Uci Upload menu, the two pages are:

services_sdk_sample_uci_upload_sample_uci.js
services_sdk_sample_uci_upload_status.js

The first file is the Sample Uci configuration tab; the second is the Status tab. Keep a copy of the original configuration file before editing it. Work on the configuration page for this task, leaving the status page available for its existing purpose.

Treat changes made directly under /www/web/js as a development experiment. After checking the behavior, package the final JavaScript and arrange for the application to restore it at startup.

Understand the generated helpers

The example relies on helpers already supplied by the device's Web interface. It does not define a standalone web application.

Helper Role
add_area_border(...) Create an expandable area corresponding to an XML area.
add_element(...) Add a configuration control for an XML child element.
get_all_val() Populate the page's values.
add_submit_btn_box() Add the Submit and Cancel buttons.

The area helper's argument order is:

add_area_border(border_desc, en_help, help_action,
                rely_condition, rely_action, cmd_script);

border_desc identifies the area; en_help controls the help icon. Use the string "false" for help_action when using the XML help content. The dependency arguments control conditional behavior, with "hide" and "show" as documented actions. The sample uses "false" for the special-purpose cmd_script argument.

The element helper's argument order is:

add_element(border_desc, top_struct_name, para_desc, type, e_type,
            en_help, help_action, rely_condition, rely_action);

top_struct_name identifies the XML root and para_desc the child element. The demonstrated control types are "input", "checkbox" and "select"; e_type is "config". Preserve the sample's distinction between Boolean false and the string "false" rather than treating them as interchangeable API arguments.

Add the form and upload function

In the device's configuration JavaScript, add an upload area and form before get_all_val(). The following complete page script retains the sample's four configuration fields and adds the upload control. It assumes that the firmware has already loaded jQuery, the page helpers, lang and the page-provided value ck_id.

$(document).ready(function () {
    $("#rightbody_content").empty();
    add_area_border("services_sdk_sample_uci_upload_sample_uci_area_0",
                    false, "false", false, false, "false");
    add_element("services_sdk_sample_uci_upload_sample_uci_area_0",
                "sample_uci_upload", "option_bool", "checkbox", "config",
                false, "false", "false", "false");
    add_element("services_sdk_sample_uci_upload_sample_uci_area_0",
                "sample_uci_upload", "option_string", "input", "config",
                false, "false", "false", "false");
    add_element("services_sdk_sample_uci_upload_sample_uci_area_0",
                "sample_uci_upload", "option_int", "input", "config",
                true, "false", "false", "false");
    add_element("services_sdk_sample_uci_upload_sample_uci_area_0",
                "sample_uci_upload", "option_enum", "select", "config",
                false, "false", "false", "false");

    add_area_border("services_sdk_sample_uci_upload_sample_uci_area_1",
                    false, "false", false, false, "false");

    var html_str = '<form id="id_json_upload_form" class="cs_upload_form" ' +
        'method="post" enctype="multipart/form-data" target="upload_feedback">' +
        '<div class="cs_arearow"><ul>' +
        '<li class="cs_coltip" id="id_json_upload_tip" title="JSON file">' +
        '<span>JSON</span></li>' +
        '<li class="cs_colval">' +
        '<span><input type="file" name="file" id="id_json_upload_val"/></span>' +
        '<span><input type="button" value="' + lang.import_desc +
        '" onclick="upload_file();"/></span>' +
        '<span><input type="hidden" name="hash" id="id_json_upload_hash"></span>' +
        '</li></ul></div></form>';

    $("#id_services_sdk_sample_uci_upload_sample_uci_area_1").append(html_str);
    get_all_val();
    add_submit_btn_box();
});

function upload_file() {
    var file = $("#id_json_upload_val")[0].files[0];
    if (!file) {
        window.alert("Choose a file before importing.");
        return;
    }

    var uploadFileName = "sample_uci.json";
    var formData = new FormData();
    formData.append('file', file, uploadFileName);
    formData.append('hash', ck_id);

    $.ajax({
        url: "/action/import_file",
        data: formData,
        type: 'POST',
        cache: false,
        contentType: false,
        processData: false,
        success: function (_res) {
            window.alert("Upload success.");
        },
        error: function (_e) {
            window.alert("Upload failed.");
        }
    });
}

The explicit no-file check prevents an empty selection from being submitted. The example keeps the destination filename fixed, so the selected host filename need not be sample_uci.json. The code does not inspect the JSON contents, validate a schema or check an application-specific success field in the response. Adapt those parts to the selected firmware endpoint and application before using this as a production import feature.

The sample obtains ck_id from the existing Web interface context. Use the existing page context; do not replace it with a hardcoded credential or remove the request's hash field to work around a failed upload.

Give the upload area a title

The new area also needs a corresponding entry in uci.xml. Keep the root, menu and tab names aligned with the JavaScript filename and element identifiers. The configuration definition for this example is:

<sample_uci_upload desc="configuration of SDK Sample Uci"
                   group="Services" group_weight="40"
                   menu="SDK Sample Uci Upload" menu_weight="901">
    <option_bool type="bool" desc="Bool Type Option" default="false"
                 tab="Sample Uci" tab_weight="0" area="General Settings" />
    <option_string default="test" desc="String Type Option" />
    <option_int type="int" desc="Integer Type Option" range="1..100"
                default="10" row_help="This is an integer type config option." />
    <option_enum type="enum" desc="Enum Type Option"
                 range="select1 select2" default="select1"
                 range_desc="Select_1 Select_2" />
    <upload_file_area desc="Upload File" area="Upload File Area" />
</sample_uci_upload>

Optional SNMP OIDs are omitted here, as in the configuration-page example. Retain the allocations approved for your own package if it uses SNMP.

Application configuration page with an Upload File Area, Choose File control and Import button

The upload control appears in its own Upload File Area beneath General Settings. The existing Submit action remains part of the configuration workflow.

An area title shown as undefined points to a mismatch between the new JavaScript area and its XML definition. Check those names before changing the upload endpoint.

Package the JavaScript and XML

Once the device-side page behaves as expected, copy the reviewed JavaScript into the SDK package source:

package/sample_uci_upload/files/services_sdk_sample_uci_upload_sample_uci.js

Use the package's files directory consistently. Preserve the rest of the supplied package; this guide does not replace its application source, complete Makefile or lifecycle handlers.

In package/sample_uci_upload/Makefile, the installation definition includes the application, lifecycle script, UCI XML, USI XML and customized JavaScript. The following is the Package/Install portion of that Makefile; each recipe line begins with a tab:

define Package/Install
	$(INSTALL_DIR) $(1)/usr/bin/
	$(INSTALL_BIN) $(PKG_BUILD_DIR)/sample_uci_upload $(1)/usr/bin/
	$(INSTALL_DIR) $(1)/etc/sdk/
	$(INSTALL_BIN) files/sdk.sh $(1)/etc/sdk/$(PKG_NAME)
	$(INSTALL_DIR) $(1)/etc/router/uci
	$(INSTALL_DATA) files/uci.xml $(1)/etc/router/uci/$(PKG_NAME).xml
	$(INSTALL_DIR) $(1)/etc/router/usi
	$(INSTALL_DATA) files/usi.xml $(1)/etc/router/usi/$(PKG_NAME).xml
	$(INSTALL_DIR) $(1)/etc/js/
	$(INSTALL_DATA) files/services_sdk_sample_uci_upload_sample_uci.js $(1)/etc/js/
endef

In the example's installed application layout, the packaged JavaScript is available at /app/etc/js/services_sdk_sample_uci_upload_sample_uci.js. Confirm that location on your target before relying on the startup copy.

Restore the page and preserve an upload at startup

Update the existing start() handler in package/sample_uci_upload/files/sdk.sh; retain its other handlers. The purpose is to restore the custom page under /www/web/js, then preserve an uploaded test file before the application starts.

The following adaptation adds explicit copy checks and handles a normal later reboot, when /tmp/upload/sample_uci.json may no longer exist. It uses the example's persistent destination /nvm/json:

TARGET_DIR=/nvm/json

start()
{
    cp -f /app/etc/js/services_sdk_sample_uci_upload_sample_uci.js \
        /www/web/js/ || return 1

    mkdir -p "${TARGET_DIR}" || return 1
    if [ -f /tmp/upload/sample_uci.json ]; then
        cp -f /tmp/upload/sample_uci.json "${TARGET_DIR}/" || return 1
    fi

    ${PROG}
    return $?
}

Keep the supplied script's initialization, including its PROG=sample_uci_upload setting. This is a handler excerpt, not a complete replacement sdk.sh.

The source and destination paths serve different purposes. The copy from /app/etc/js restores the customized page. The copy from /tmp/upload moves a newly uploaded test file to persistent storage. When no new temporary upload exists, the handler leaves any existing persistent file in place.

Important: The cp -f operation replaces a file of the same name. Use a disposable fixture for this experiment. A production import path needs a defined validation, replacement and recovery policy; this sample does not implement an atomic update or rollback. An upload that remains in /tmp/upload can also be copied again on a later application restart before the device reboots.

Build the package in the SDK workspace, export the RPK and install it on the matching development device using the package build and installation workflow. Record the build and installation result before testing the upload sequence.

Run the complete upload sequence

  1. Open the application's Sample Uci tab in the device Web interface. Confirm that the custom control and Upload File Area title are present.
  2. Select the test file with Choose File, then select Import. Record the displayed result.
  3. On the development device, check that /tmp/upload/sample_uci.json exists and contains the intended test file. An alert by itself is insufficient evidence.
  4. In this sample's configuration workflow, change a harmless application-owned setting and select Submit. This makes Save & Apply available for the pending configuration change.
  5. Select Save & Apply and perform the application's restart sequence. Confirm that the start() handler actually ran; do not assume that selecting the button proves the copy occurred.
  6. Check /nvm/json/sample_uci.json, then check that the application reads the intended data. Record those as separate outcomes.
  7. When a reboot is suitable for the development device, verify that the persistent file remains and that the customized upload control is restored. A successful browser upload before reboot does not prove either outcome.

This workflow changes application configuration to reach the sample's apply/restart path. Use a deliberately harmless test setting, and restore its original value after the experiment if appropriate. Do not change unrelated connectivity settings merely to enable Save & Apply.

Diagnose a failed stage

Symptom Check first
No upload control appears Configuration-tab filename; placement before get_all_val(); JavaScript loading errors; source file restored at startup
Area heading is undefined Matching XML area entry and generated area identifier
Upload request fails Authenticated page context, ck_id, the firmware endpoint and response; do not bypass session checks
Alert says success but the application has no file Temporary path, fixed destination filename and the later persistent-copy stage
Upload disappears after reboot Whether the file was copied out of /tmp before reboot
Page customization disappears after restart Packaged /app/etc/js file and its startup copy to /www/web/js
Persistent file exists but the application rejects it Application-specific format and validation; transfer and persistence do not validate JSON semantics

Keep the browser result, device file checks, startup result and application result together in the test record. Validate the JavaScript, XML and startup adaptation with the selected SDK, firmware and device before releasing your application.

For the development environment and target-selection workflow, see the RobustOS SDK guide.