More work on the API + documentation
Changed some endpoints again (removed "/control" path element) and made API spit out only raw data (e.g. seconds, millimeters, unix timestamps etc) instead of formatted versions. Modified frontend to take care of formatting this data itself.
This commit is contained in:
parent
097e398efc
commit
538338abfe
28 changed files with 740 additions and 377 deletions
|
|
@ -11,7 +11,7 @@ Connection handling
|
|||
Get connection settings
|
||||
=======================
|
||||
|
||||
.. http:get:: /api/control/connection
|
||||
.. http:get:: /api/connection
|
||||
|
||||
Retrieve the current connection settings, including information regarding the available baudrates and
|
||||
serial ports and the current connection state.
|
||||
|
|
@ -54,7 +54,7 @@ Get connection settings
|
|||
Issue a connection command
|
||||
==========================
|
||||
|
||||
.. http:post:: /api/control/connection
|
||||
.. http:post:: /api/connection
|
||||
|
||||
Issue a connection command. Currently available command are:
|
||||
|
||||
|
|
|
|||
|
|
@ -36,23 +36,25 @@ Retrieve all files
|
|||
"files": [
|
||||
{
|
||||
"name": "whistle_v2.gcode",
|
||||
"bytes": 1468987,
|
||||
"size": "1.4MB",
|
||||
"date": "2013-05-21 23:15",
|
||||
"size": 1468987,
|
||||
"date": 1378847754,
|
||||
"origin": "local",
|
||||
"refs": {
|
||||
"resource": "http://example.com/api/files/local/whistle_v2.gcode",
|
||||
"download": "http://example.com/downloads/files/local/whistle_v2.gcode"
|
||||
},
|
||||
"gcodeAnalysis": {
|
||||
"estimatedPrintTime": "00:31:40",
|
||||
"filament": "0.79m"
|
||||
"estimatedPrintTime": 1188,
|
||||
"filament": {
|
||||
"length": 810,
|
||||
"volume": 5.36
|
||||
}
|
||||
},
|
||||
"print": {
|
||||
"failure": 4,
|
||||
"success": 23,
|
||||
"last": {
|
||||
"date": "2013-11-18 18:00",
|
||||
"date": 1387144346,
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
|
|
@ -70,7 +72,7 @@ Retrieve all files
|
|||
|
||||
:statuscode 200: No error
|
||||
|
||||
.. _sec-api-fileops-retrievespecific:
|
||||
.. _sec-api-fileops-retrievelocation:
|
||||
|
||||
Retrieve files from specific location
|
||||
=====================================
|
||||
|
|
@ -100,23 +102,25 @@ Retrieve files from specific location
|
|||
"files": [
|
||||
{
|
||||
"name": "whistle_v2.gcode",
|
||||
"bytes": 1468987,
|
||||
"size": "1.4MB"
|
||||
"date": "2013-05-21 23:15",
|
||||
"size": 1468987,
|
||||
"date": 1378847754,
|
||||
"origin": "local",
|
||||
"refs": {
|
||||
"resource": "http://example.com/api/files/local/whistle_v2.gcode",
|
||||
"download": "http://example.com/downloads/files/local/whistle_v2.gcode"
|
||||
},
|
||||
"gcodeAnalysis": {
|
||||
"estimatedPrintTime": "00:31:40",
|
||||
"filament": "0.79m"
|
||||
"estimatedPrintTime": 1188,
|
||||
"filament": {
|
||||
"length": 810,
|
||||
"volume": 5.36
|
||||
}
|
||||
},
|
||||
"print": {
|
||||
"failure": 4,
|
||||
"success": 23,
|
||||
"last": {
|
||||
"date": "2013-11-18 18:00",
|
||||
"date": 1387144346,
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
|
|
@ -259,23 +263,25 @@ Retrieve a specific file's information
|
|||
|
||||
{
|
||||
"name": "whistle_v2.gcode",
|
||||
"bytes": 1468987,
|
||||
"size": "1.4MB"
|
||||
"date": "2013-05-21 23:15",
|
||||
"size": 1468987,
|
||||
"date": 1378847754,
|
||||
"origin": "local",
|
||||
"refs": {
|
||||
"resource": "http://example.com/api/files/local/whistle_v2.gcode",
|
||||
"download": "http://example.com/downloads/files/local/whistle_v2.gcode"
|
||||
},
|
||||
"gcodeAnalysis": {
|
||||
"estimatedPrintTime": "00:31:40",
|
||||
"filament": "0.79m"
|
||||
"estimatedPrintTime": 1188,
|
||||
"filament": {
|
||||
"length": 810,
|
||||
"volume": 5.36
|
||||
}
|
||||
},
|
||||
"print": {
|
||||
"failure": 4,
|
||||
"success": 23,
|
||||
"last": {
|
||||
"date": "2013-11-18 18:00",
|
||||
"date": 1387144346,
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
|
|
@ -383,7 +389,7 @@ Retrieve response
|
|||
* - ``free``
|
||||
- 0..1
|
||||
- String
|
||||
- The amount of disk space available in the local disk space (refers to OctoPrint's ``uploads`` folder). Only
|
||||
- The amount of disk space in bytes available in the local disk space (refers to OctoPrint's ``uploads`` folder). Only
|
||||
returned if file list was requested for origin ``local`` or all origins.
|
||||
|
||||
.. _sec-api-fileops-datamodel-uploadresponse:
|
||||
|
|
@ -438,18 +444,14 @@ File information
|
|||
- 1
|
||||
- String
|
||||
- The name of the file
|
||||
* - ``bytes``
|
||||
* - ``size``
|
||||
- 0..1
|
||||
- Number
|
||||
- The size of the file in bytes. Only available for ``local`` files.
|
||||
* - ``size``
|
||||
- 0..1
|
||||
- String
|
||||
- The size of the file in a human readable format. Only available for ``local`` files.
|
||||
* - ``date``
|
||||
- 0..1
|
||||
- String representing a date and time in the format ``YYYY-MM-DD HH:mm``
|
||||
- The date and time this files was uploaded. Only available for ``local`` files.
|
||||
- Unix timestamp
|
||||
- The timestamp when this file was uploaded. Only available for ``local`` files.
|
||||
* - ``origin``
|
||||
- 1
|
||||
- String, either ``local`` or ``sdcard``
|
||||
|
|
@ -457,7 +459,7 @@ File information
|
|||
printer's SD card (if available)
|
||||
* - ``refs``
|
||||
- 0..1
|
||||
- :ref:`<sec-api-fileops-datamodel-ref>`
|
||||
- :ref:`sec-api-fileops-datamodel-ref`
|
||||
- References relevant to this file
|
||||
* - ``gcodeAnalysis``
|
||||
- 0..1
|
||||
|
|
@ -482,14 +484,21 @@ GCODE analysis information
|
|||
- Type
|
||||
- Description
|
||||
* - ``estimatedPrintTime``
|
||||
- 1
|
||||
- String representing a duration in the format ``HH:mm:ss``
|
||||
- The estimated print time of the file
|
||||
- 0..1
|
||||
- Integer
|
||||
- The estimated print time of the file, in seconds
|
||||
* - ``filament``
|
||||
- 1
|
||||
- String
|
||||
- The estimated usage of filament (length in meters and volume in cubic centimeters) in a human readable format.
|
||||
Example: ``1.89m / 11.90cm³``
|
||||
- 0..1
|
||||
- Object
|
||||
- The estimated usage of filament
|
||||
* - ``filament.length``
|
||||
- 0..1
|
||||
- Integer
|
||||
- The length of filament used, in mm
|
||||
* - ``filament.volume``
|
||||
- 0..1
|
||||
- Float
|
||||
- The volume of filament used, in cm³
|
||||
|
||||
|
||||
.. _sec-api-fileops-datamodel-prints:
|
||||
|
|
@ -519,8 +528,8 @@ Print information
|
|||
- Information regarding the last print on record for the file
|
||||
* - ``last.date``
|
||||
- 1
|
||||
- String representing a date and time in the format ``YYYY-MM-DD HH:mm``
|
||||
- Date and time when the file was printed last
|
||||
- Unix timestamp
|
||||
- Timestamp when this file was printed last
|
||||
* - ``last.success``
|
||||
- 1
|
||||
- Boolean
|
||||
|
|
|
|||
|
|
@ -11,5 +11,5 @@ API Documentation
|
|||
fileops.rst
|
||||
connection.rst
|
||||
printer.rst
|
||||
jobs.rst
|
||||
job.rst
|
||||
|
||||
|
|
|
|||
248
docs/api/job.rst
Normal file
248
docs/api/job.rst
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
.. _sec-api-jobs:
|
||||
|
||||
**************
|
||||
Job operations
|
||||
**************
|
||||
|
||||
.. contents::
|
||||
|
||||
.. _sec-api-jobs-command:
|
||||
|
||||
Issue a job command
|
||||
===================
|
||||
|
||||
.. http:post:: /api/job
|
||||
|
||||
Job commands allow starting, pausing and cancelling print jobs. Available commands are:
|
||||
|
||||
start
|
||||
Starts the print of the currently selected file. For selecting a file, see :ref:`Issue a file command <sec-api-fileops-filecommand>`.
|
||||
If a print job is already active, a :http:statuscode:`409` will be returned.
|
||||
|
||||
restart
|
||||
Restart the print of the currently selected file from the beginning. There must be an active print job for this to work
|
||||
and the print job must currently be paused. If either is not the case, a :http:statuscode:`409` will be returned.
|
||||
|
||||
pause
|
||||
Pauses/unpauses the current print job. If no print job is active (either paused or printing), a :http:statuscode:`409`
|
||||
will be returned.
|
||||
|
||||
cancel
|
||||
Cancels the current print job. If no print job is active (either paused or printing), a :http:statuscode:`409`
|
||||
will be returned.
|
||||
|
||||
Upon success, a status code of :http:statuscode:`204` and an empty body is returned.
|
||||
|
||||
**Example Start Request**
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/control/job HTTP/1.1
|
||||
Host: example.com
|
||||
Content-Type: application/json
|
||||
X-Api-Key: abcdef...
|
||||
|
||||
{
|
||||
"command": "start"
|
||||
}
|
||||
|
||||
**Example Restart Request**
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/control/job HTTP/1.1
|
||||
Host: example.com
|
||||
Content-Type: application/json
|
||||
X-Api-Key: abcdef...
|
||||
|
||||
{
|
||||
"command": "restart"
|
||||
}
|
||||
|
||||
**Example Pause Request**
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/control/job HTTP/1.1
|
||||
Host: example.com
|
||||
Content-Type: application/json
|
||||
X-Api-Key: abcdef...
|
||||
|
||||
{
|
||||
"command": "pause"
|
||||
}
|
||||
|
||||
**Example Cancel Request**
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/control/job HTTP/1.1
|
||||
Host: example.com
|
||||
Content-Type: application/json
|
||||
X-Api-Key: abcdef...
|
||||
|
||||
{
|
||||
"command": "cancel"
|
||||
}
|
||||
|
||||
:json string command: The command to issue, either ``start``, ``restart``, ``pause`` or ``cancel``
|
||||
:statuscode 204: No error
|
||||
:statuscode 409: If the printer is not operational or the current print job state does not match the preconditions
|
||||
for the command.
|
||||
|
||||
.. _sec-api-job-information:
|
||||
|
||||
Retrieve information about the current job
|
||||
==========================================
|
||||
|
||||
.. http:get:: /api/job
|
||||
|
||||
Retrieve information about the current job (if there is one).
|
||||
|
||||
Returns a :http:statuscode:`200` with a :ref:`sec-api-job-datamodel-response` in the body.
|
||||
|
||||
**Example Request**
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /api/job HTTP/1.1
|
||||
Host: example.com
|
||||
|
||||
**Example Response**
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"job": {
|
||||
"file": {
|
||||
"name": "whistle_v2.gcode",
|
||||
"origin": "local",
|
||||
"size": 1468987,
|
||||
"date": 1378847754
|
||||
},
|
||||
"estimatedPrintTime": 8811,
|
||||
"filament": {
|
||||
"length": 810,
|
||||
"volume": 5.36
|
||||
}
|
||||
},
|
||||
"progress": {
|
||||
"completion": 23,
|
||||
"filepos": 337942,
|
||||
"printTime": 276,
|
||||
"printTimeLeft": 912
|
||||
}
|
||||
}
|
||||
|
||||
:statuscode 200: No error
|
||||
|
||||
.. _sec-api-job-datamodel:
|
||||
|
||||
Datamodel
|
||||
=========
|
||||
|
||||
.. _sec-api-job-datamodel-response:
|
||||
|
||||
Job information response
|
||||
------------------------
|
||||
|
||||
.. list-table::
|
||||
:widths: 15 5 10 30
|
||||
:header-rows: 1
|
||||
|
||||
* - Name
|
||||
- Multiplicity
|
||||
- Type
|
||||
- Description
|
||||
* - ``job``
|
||||
- 1
|
||||
- :ref:`sec-api-job-datamodel-job`
|
||||
- Information regarding the target of the current print job
|
||||
* - ``progress``
|
||||
- 1
|
||||
- :ref:`sec-api-job-datamodel-progress`
|
||||
- Information regarding the progress of the current print job
|
||||
|
||||
.. _sec-api-job-datamodel-job:
|
||||
|
||||
Job information
|
||||
---------------
|
||||
|
||||
.. list-table::
|
||||
:widths: 15 5 10 30
|
||||
:header-rows: 1
|
||||
|
||||
* - Name
|
||||
- Multiplicity
|
||||
- Type
|
||||
- Description
|
||||
* - ``file``
|
||||
- 1
|
||||
- Object
|
||||
- The file that is the target of the current print job
|
||||
* - ``file.name``
|
||||
- 1
|
||||
- String
|
||||
- The file's name
|
||||
* - ``file.origin``
|
||||
- 1
|
||||
- String, either ``local`` or ``sdcard``
|
||||
- The file's origin, either ``local`` or ``sdcard``
|
||||
* - ``file.size``
|
||||
- 0..1
|
||||
- Integer
|
||||
- The file's size, in bytes. Only available for files stored locally.
|
||||
* - ``file.date``
|
||||
- 0..1
|
||||
- Unix timestamp
|
||||
- The file's upload date. Only available for files stored locally.
|
||||
* - ``estimatedPrintTime``
|
||||
- 0..1
|
||||
- Integer
|
||||
- The estimated print time for the file, in seconds.
|
||||
* - ``filament``
|
||||
- 0..1
|
||||
- Object
|
||||
- Information regarding the estimated filament usage of the print job
|
||||
* - ``filament.length``
|
||||
- 0..1
|
||||
- Integer
|
||||
- Length of filament used, in mm
|
||||
* - ``filament.volume``
|
||||
- 0..1
|
||||
- Float
|
||||
- Volume of filament used, in cm³
|
||||
|
||||
.. _sec-api-job-datamodel-progress:
|
||||
|
||||
Progress information
|
||||
--------------------
|
||||
|
||||
.. list-table::
|
||||
:widths: 15 5 10 30
|
||||
:header-rows: 1
|
||||
|
||||
* - Name
|
||||
- Multiplicity
|
||||
- Type
|
||||
- Description
|
||||
* - ``completion``
|
||||
- 1
|
||||
- Integer
|
||||
- Percentage of completion of the current print job
|
||||
* - ``filepos``
|
||||
- 1
|
||||
- Integer
|
||||
- Current position in the file being printed, in bytes from the beginning
|
||||
* - ``printTime``
|
||||
- 1
|
||||
- Integer
|
||||
- Time already spent printing, in seconds
|
||||
* - ``printTimeLeft``
|
||||
- 1
|
||||
- Integer
|
||||
- Estimate of time left to print, in seconds
|
||||
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
.. _sec-api-jobs:
|
||||
|
||||
***********
|
||||
Job Control
|
||||
***********
|
||||
|
||||
.. contents::
|
||||
|
||||
.. _sec-api-jobs-command:
|
||||
|
||||
Issue a job command
|
||||
===================
|
||||
|
||||
.. http:post:: /api/control/job
|
||||
|
||||
Job commands allow starting, pausing and cancelling print jobs. Available commands are:
|
||||
|
||||
start
|
||||
Starts the print of the currently selected file. For selecting a file, see :ref:`Issue a file command <sec-api-fileops-filecommand>`.
|
||||
If a print job is already active, a :http:statuscode:`409` will be returned.
|
||||
|
||||
restart
|
||||
Restart the print of the currently selected file from the beginning. There must be an active print job for this to work
|
||||
and the print job must currently be paused. If either is not the case, a :http:statuscode:`409` will be returned.
|
||||
|
||||
pause
|
||||
Pauses/unpauses the current print job. If no print job is active (either paused or printing), a :http:statuscode:`409`
|
||||
will be returned.
|
||||
|
||||
cancel
|
||||
Cancels the current print job. If no print job is active (either paused or printing), a :http:statuscode:`409`
|
||||
will be returned.
|
||||
|
||||
Upon success, a status code of :http:statuscode:`204` and an empty body is returned.
|
||||
|
||||
**Example Start Request**
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/control/job HTTP/1.1
|
||||
Host: example.com
|
||||
Content-Type: application/json
|
||||
X-Api-Key: abcdef...
|
||||
|
||||
{
|
||||
"command": "start"
|
||||
}
|
||||
|
||||
**Example Restart Request**
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/control/job HTTP/1.1
|
||||
Host: example.com
|
||||
Content-Type: application/json
|
||||
X-Api-Key: abcdef...
|
||||
|
||||
{
|
||||
"command": "restart"
|
||||
}
|
||||
|
||||
**Example Pause Request**
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/control/job HTTP/1.1
|
||||
Host: example.com
|
||||
Content-Type: application/json
|
||||
X-Api-Key: abcdef...
|
||||
|
||||
{
|
||||
"command": "pause"
|
||||
}
|
||||
|
||||
**Example Cancel Request**
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/control/job HTTP/1.1
|
||||
Host: example.com
|
||||
Content-Type: application/json
|
||||
X-Api-Key: abcdef...
|
||||
|
||||
{
|
||||
"command": "cancel"
|
||||
}
|
||||
|
||||
:json string command: The command to issue, either ``start``, ``restart``, ``pause`` or ``cancel``
|
||||
:statuscode 204: No error
|
||||
:statuscode 409: If the printer is not operational or the current print job state does not match the preconditions
|
||||
for the command.
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
.. _sec-api-printer:
|
||||
|
||||
***************
|
||||
Printer Control
|
||||
***************
|
||||
******************
|
||||
Printer operations
|
||||
******************
|
||||
|
||||
.. contents::
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ SD card
|
|||
Issue a print head command
|
||||
==========================
|
||||
|
||||
.. http:post:: /api/control/printer/printhead
|
||||
.. http:post:: /api/printer/printhead
|
||||
|
||||
Print head commands allow jogging and homing the print head in all three axes. Available commands are:
|
||||
|
||||
|
|
@ -52,7 +52,7 @@ Issue a print head command
|
|||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/control/printer/printhead HTTP/1.1
|
||||
POST /api/printer/printhead HTTP/1.1
|
||||
Host: example.com
|
||||
Content-Type: application/json
|
||||
X-Api-Key: abcdef...
|
||||
|
|
@ -70,7 +70,7 @@ Issue a print head command
|
|||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/control/printer/printhead HTTP/1.1
|
||||
POST /api/printer/printhead HTTP/1.1
|
||||
Host: example.com
|
||||
Content-Type: application/json
|
||||
X-Api-Key: abcdef...
|
||||
|
|
@ -95,7 +95,7 @@ Issue a print head command
|
|||
Issue a heater command
|
||||
======================
|
||||
|
||||
.. http:post:: /api/control/printer/heater
|
||||
.. http:post:: /api/printer/heater
|
||||
|
||||
Heater commands allow setting the temperature and temperature offsets for the printer's hotend and bed. Available
|
||||
commands are:
|
||||
|
|
@ -127,7 +127,7 @@ Issue a heater command
|
|||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/control/printer/heater HTTP/1.1
|
||||
POST /api/printer/heater HTTP/1.1
|
||||
Host: example.com
|
||||
Content-Type: application/json
|
||||
X-Api-Key: abcdef...
|
||||
|
|
@ -146,7 +146,7 @@ Issue a heater command
|
|||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/control/printer/heater HTTP/1.1
|
||||
POST /api/printer/heater HTTP/1.1
|
||||
Host: example.com
|
||||
Content-Type: application/json
|
||||
X-Api-Key: abcdef...
|
||||
|
|
@ -173,7 +173,7 @@ Issue a heater command
|
|||
Issue a feeder command
|
||||
======================
|
||||
|
||||
.. http:post:: /api/control/printer/feeder
|
||||
.. http:post:: /api/printer/feeder
|
||||
|
||||
Feeder commands allow extrusion/extraction of filament. Available commands are:
|
||||
|
||||
|
|
@ -193,7 +193,7 @@ Issue a feeder command
|
|||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/control/printer/feeder HTTP/1.1
|
||||
POST /api/printer/feeder HTTP/1.1
|
||||
Host: example.com
|
||||
Content-Type: application/json
|
||||
X-Api-Key: abcdef...
|
||||
|
|
@ -209,7 +209,7 @@ Issue a feeder command
|
|||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/control/printer/feeder HTTP/1.1
|
||||
POST /api/printer/feeder HTTP/1.1
|
||||
Host: example.com
|
||||
Content-Type: application/json
|
||||
X-Api-Key: abcdef...
|
||||
|
|
@ -230,7 +230,7 @@ Issue a feeder command
|
|||
Issue a SD command
|
||||
==================
|
||||
|
||||
.. http:post:: /api/control/printer/sd
|
||||
.. http:post:: /api/printer/sd
|
||||
|
||||
SD commands allow initialization, refresh and release of the printer's SD card (if available).
|
||||
|
||||
|
|
@ -239,7 +239,7 @@ Issue a SD command
|
|||
init
|
||||
Initializes the printer's SD card, making it available for use. This also includes an initial retrieval of the
|
||||
list of files currently stored on the SD card, so after issueing that command a :ref:`retrieval of the files
|
||||
on SD card <sec-api-fileops-retrieveorigin>` will return a successful result.
|
||||
on SD card <sec-api-fileops-retrievelocation>` will return a successful result.
|
||||
|
||||
.. note::
|
||||
If OctoPrint detects the availability of a SD card on the printer during connection, it will automatically attempt
|
||||
|
|
@ -260,7 +260,7 @@ Issue a SD command
|
|||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/control/printer/sd HTTP/1.1
|
||||
POST /api/printer/sd HTTP/1.1
|
||||
Host: example.com
|
||||
Content-Type: application/json
|
||||
X-Api-Key: abcdef...
|
||||
|
|
@ -273,7 +273,7 @@ Issue a SD command
|
|||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/control/printer/sd HTTP/1.1
|
||||
POST /api/printer/sd HTTP/1.1
|
||||
Host: example.com
|
||||
Content-Type: application/json
|
||||
X-Api-Key: abcdef...
|
||||
|
|
@ -286,7 +286,7 @@ Issue a SD command
|
|||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/control/printer/sd HTTP/1.1
|
||||
POST /api/printer/sd HTTP/1.1
|
||||
Host: example.com
|
||||
Content-Type: application/json
|
||||
X-Api-Key: abcdef...
|
||||
|
|
@ -305,7 +305,7 @@ Issue a SD command
|
|||
Retrieve the current SD state
|
||||
=============================
|
||||
|
||||
.. http:get:: /api/control/printer/sd
|
||||
.. http:get:: /api/printer/sd
|
||||
|
||||
Retrieves the current state of the printer's SD card. For this request no authentication is needed.
|
||||
|
||||
|
|
@ -318,7 +318,7 @@ Retrieve the current SD state
|
|||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /api/control/printer/sd HTTP/1.1
|
||||
GET /api/printer/sd HTTP/1.1
|
||||
Host: example.com
|
||||
|
||||
**Example Response**
|
||||
|
|
|
|||
|
|
@ -254,7 +254,7 @@ class CommandTrigger(GenericEventListener):
|
|||
try:
|
||||
processedCommand = self._processCommand(command, payload)
|
||||
self.executeCommand(processedCommand)
|
||||
except KeyError:
|
||||
except KeyError, e:
|
||||
self._logger.warn("There was an error processing one or more placeholders in the following command: %s" % command)
|
||||
|
||||
def executeCommand(self, command):
|
||||
|
|
@ -292,7 +292,7 @@ class CommandTrigger(GenericEventListener):
|
|||
params["__currentZ"] = str(currentData["currentZ"])
|
||||
|
||||
if "job" in currentData.keys() and currentData["job"] is not None:
|
||||
params["__filename"] = currentData["job"]["filename"]
|
||||
params["__filename"] = currentData["job"]["file"]["name"]
|
||||
if "progress" in currentData.keys() and currentData["progress"] is not None \
|
||||
and "progress" in currentData["progress"].keys() and currentData["progress"]["progress"] is not None:
|
||||
params["__progress"] = str(round(currentData["progress"]["progress"] * 100))
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
# coding=utf-8
|
||||
import re
|
||||
|
||||
__author__ = "Gina Häußge <osd@foosel.net>"
|
||||
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
|
||||
|
||||
import os
|
||||
import Queue
|
||||
import threading
|
||||
import datetime
|
||||
import yaml
|
||||
import time
|
||||
import logging
|
||||
|
|
@ -14,6 +15,7 @@ import octoprint.util.gcodeInterpreter as gcodeInterpreter
|
|||
|
||||
from octoprint.settings import settings
|
||||
from octoprint.events import eventManager, Events
|
||||
from octoprint.filemanager.destinations import FileDestinations
|
||||
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
|
|
@ -107,12 +109,16 @@ class GcodeManager:
|
|||
analysisResult = {}
|
||||
dirty = False
|
||||
if gcode.totalMoveTimeMinute:
|
||||
analysisResult["estimatedPrintTime"] = util.getFormattedTimeDelta(datetime.timedelta(minutes=gcode.totalMoveTimeMinute))
|
||||
analysisResult["estimatedPrintTime"] = gcode.totalMoveTimeMinute * 60
|
||||
dirty = True
|
||||
if gcode.extrusionAmount:
|
||||
analysisResult["filament"] = "%.2fm" % (gcode.extrusionAmount / 1000)
|
||||
analysisResult["filament"] = {
|
||||
"length": gcode.extrusionAmount
|
||||
}
|
||||
if gcode.calculateVolumeCm3():
|
||||
analysisResult["filament"] += " / %.2fcm³" % gcode.calculateVolumeCm3()
|
||||
analysisResult["filament"].update({
|
||||
"volume": gcode.calculateVolumeCm3()
|
||||
})
|
||||
dirty = True
|
||||
|
||||
if dirty:
|
||||
|
|
@ -128,9 +134,58 @@ class GcodeManager:
|
|||
with self._metadataFileAccessMutex:
|
||||
with open(self._metadataFile, "r") as f:
|
||||
self._metadata = yaml.safe_load(f)
|
||||
|
||||
if self._metadata is None:
|
||||
self._metadata = {}
|
||||
|
||||
# TODO: Remove in a couple of versions (2013-12-21)
|
||||
self._migrateMetadata()
|
||||
|
||||
def _migrateMetadata(self):
|
||||
self._logger.info("Migrating metadata if necessary...")
|
||||
|
||||
printTimeRe = r"(\d+):(\d{2}):(\d{2})"
|
||||
filamentRe = r"(\d*\.\d+)m(\s/\s(\d*\.\d+)cm.)?"
|
||||
|
||||
hoursToSeconds = 60 * 60
|
||||
minutesToSeconds = 60
|
||||
|
||||
updateCount = 0
|
||||
for metadata in self._metadata.values():
|
||||
if not "gcodeAnalysis" in metadata:
|
||||
continue
|
||||
|
||||
updated = False
|
||||
if "estimatedPrintTime" in metadata["gcodeAnalysis"]:
|
||||
estimatedPrintTime = metadata["gcodeAnalysis"]["estimatedPrintTime"]
|
||||
if isinstance(estimatedPrintTime, (str, unicode)):
|
||||
match = re.match(printTimeRe, estimatedPrintTime)
|
||||
if match:
|
||||
metadata["gcodeAnalysis"]["estimatedPrintTime"] = int(match.group(1)) * hoursToSeconds + int(match.group(2)) * minutesToSeconds + int(match.group(3))
|
||||
self._metadataDirty = True
|
||||
updated = True
|
||||
if "filament" in metadata["gcodeAnalysis"]:
|
||||
filament = metadata["gcodeAnalysis"]["filament"]
|
||||
if isinstance(filament, (str, unicode)):
|
||||
match = re.match(filamentRe, filament)
|
||||
if match:
|
||||
metadata["gcodeAnalysis"]["filament"] = {
|
||||
"length": int(float(match.group(1)) * 1000)
|
||||
}
|
||||
if match.group(3) is not None:
|
||||
metadata["gcodeAnalysis"]["filament"].update({
|
||||
"volume": float(match.group(3))
|
||||
})
|
||||
self._metadataDirty = True
|
||||
updated = True
|
||||
|
||||
if updated:
|
||||
updateCount += 1
|
||||
|
||||
self._saveMetadata()
|
||||
|
||||
self._logger.info("Updated %d sets of metadata to new format" % updateCount)
|
||||
|
||||
def _saveMetadata(self, force=False):
|
||||
if not self._metadataDirty and not force:
|
||||
return
|
||||
|
|
@ -330,9 +385,9 @@ class GcodeManager:
|
|||
statResult = os.stat(absolutePath)
|
||||
fileData = {
|
||||
"name": filename,
|
||||
"size": util.getFormattedSize(statResult.st_size),
|
||||
"bytes": statResult.st_size,
|
||||
"date": util.getFormattedDateTime(datetime.datetime.fromtimestamp(statResult.st_ctime))
|
||||
"size": statResult.st_size,
|
||||
"origin": FileDestinations.LOCAL,
|
||||
"date": int(statResult.st_ctime)
|
||||
}
|
||||
|
||||
# enrich with additional metadata from analysis if available
|
||||
|
|
@ -340,18 +395,18 @@ class GcodeManager:
|
|||
for key in self._metadata[filename].keys():
|
||||
if key == "prints":
|
||||
val = self._metadata[filename][key]
|
||||
formattedLast = None
|
||||
last = None
|
||||
if "last" in val and val["last"] is not None:
|
||||
formattedLast = {
|
||||
"date": util.getFormattedDateTime(datetime.datetime.fromtimestamp(val["last"]["date"])),
|
||||
last = {
|
||||
"date": val["last"]["date"],
|
||||
"success": val["last"]["success"]
|
||||
}
|
||||
formattedPrints = {
|
||||
prints = {
|
||||
"success": val["success"],
|
||||
"failure": val["failure"],
|
||||
"last": formattedLast
|
||||
"last": last
|
||||
}
|
||||
fileData["prints"] = formattedPrints
|
||||
fileData["prints"] = prints
|
||||
else:
|
||||
fileData[key] = self._metadata[filename][key]
|
||||
|
||||
|
|
|
|||
|
|
@ -92,8 +92,20 @@ class Printer():
|
|||
)
|
||||
self._stateMonitor.reset(
|
||||
state={"state": None, "stateString": self.getStateString(), "flags": self._getStateFlags()},
|
||||
jobData={"filename": None, "filesize": None, "estimatedPrintTime": None, "filament": None},
|
||||
progress={"progress": None, "filepos": None, "printTime": None, "printTimeLeft": None},
|
||||
jobData={
|
||||
"file": {
|
||||
"name": None,
|
||||
"size": None,
|
||||
"origin": None,
|
||||
"date": None
|
||||
},
|
||||
"estimatedPrintTime": None,
|
||||
"filament": {
|
||||
"length": None,
|
||||
"volume": None
|
||||
}
|
||||
},
|
||||
progress={"completion": None, "filepos": None, "printTime": None, "printTimeLeft": None},
|
||||
currentZ=None
|
||||
)
|
||||
|
||||
|
|
@ -259,11 +271,7 @@ class Printer():
|
|||
|
||||
def _setCurrentZ(self, currentZ):
|
||||
self._currentZ = currentZ
|
||||
|
||||
formattedCurrentZ = None
|
||||
if self._currentZ:
|
||||
formattedCurrentZ = "%.2f mm" % (self._currentZ)
|
||||
self._stateMonitor.setCurrentZ(formattedCurrentZ)
|
||||
self._stateMonitor.setCurrentZ(self._currentZ)
|
||||
|
||||
def _setState(self, state):
|
||||
self._state = state
|
||||
|
|
@ -282,22 +290,15 @@ class Printer():
|
|||
self._printTime = printTime
|
||||
self._printTimeLeft = printTimeLeft
|
||||
|
||||
formattedPrintTime = None
|
||||
if (self._printTime):
|
||||
formattedPrintTime = util.getFormattedTimeDelta(datetime.timedelta(seconds=self._printTime))
|
||||
|
||||
formattedPrintTimeLeft = None
|
||||
if (self._printTimeLeft):
|
||||
formattedPrintTimeLeft = util.getFormattedTimeDelta(datetime.timedelta(minutes=self._printTimeLeft))
|
||||
|
||||
formattedFilePos = None
|
||||
if (filepos):
|
||||
formattedFilePos = util.getFormattedSize(filepos)
|
||||
|
||||
self._stateMonitor.setProgress({"progress": self._progress, "filepos": formattedFilePos, "printTime": formattedPrintTime, "printTimeLeft": formattedPrintTimeLeft})
|
||||
self._stateMonitor.setProgress({
|
||||
"completion": int(self._progress * 100) if self._progress is not None else None,
|
||||
"filepos": filepos,
|
||||
"printTime": int(self._printTime) if self._printTime is not None else None,
|
||||
"printTimeLeft": int(self._printTimeLeft * 60) if self._printTimeLeft is not None else None
|
||||
})
|
||||
|
||||
def _addTemperatureData(self, temp, bedTemp, targetTemp, bedTargetTemp):
|
||||
currentTimeUtc = int(time.time() * 1000)
|
||||
currentTimeUtc = int(time.time())
|
||||
|
||||
self._temps["actual"].append((currentTimeUtc, temp))
|
||||
self._temps["target"].append((currentTimeUtc, targetTemp))
|
||||
|
|
@ -321,21 +322,14 @@ class Printer():
|
|||
else:
|
||||
self._selectedFile = None
|
||||
|
||||
formattedFilename = None
|
||||
formattedFilesize = None
|
||||
estimatedPrintTime = None
|
||||
fileMTime = None
|
||||
date = None
|
||||
filament = None
|
||||
if filename:
|
||||
formattedFilename = os.path.basename(filename)
|
||||
|
||||
# Use a string for mtime because it could be float and the
|
||||
# javascript needs to exact match
|
||||
if not sd:
|
||||
fileMTime = str(os.stat(filename).st_mtime)
|
||||
|
||||
if filesize:
|
||||
formattedFilesize = util.getFormattedSize(filesize)
|
||||
date = int(os.stat(filename).st_ctime)
|
||||
|
||||
fileData = self._gcodeManager.getFileData(filename)
|
||||
if fileData is not None and "gcodeAnalysis" in fileData.keys():
|
||||
|
|
@ -344,7 +338,16 @@ class Printer():
|
|||
if "filament" in fileData["gcodeAnalysis"].keys():
|
||||
filament = fileData["gcodeAnalysis"]["filament"]
|
||||
|
||||
self._stateMonitor.setJobData({"filename": formattedFilename, "filesize": formattedFilesize, "estimatedPrintTime": estimatedPrintTime, "filament": filament, "sd": sd, "mtime": fileMTime})
|
||||
self._stateMonitor.setJobData({
|
||||
"file": {
|
||||
"name": os.path.basename(filename),
|
||||
"origin": FileDestinations.SDCARD if sd else FileDestinations.LOCAL,
|
||||
"size": filesize,
|
||||
"date": date
|
||||
},
|
||||
"estimatedPrintTime": estimatedPrintTime,
|
||||
"filament": filament,
|
||||
})
|
||||
|
||||
def _sendInitialStateUpdate(self, callback):
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -19,7 +19,9 @@ from octoprint.settings import settings as s, valid_boolean_trues
|
|||
|
||||
api = Blueprint("api", __name__)
|
||||
|
||||
from . import control as api_control
|
||||
from . import printer as api_printer
|
||||
from . import job as api_job
|
||||
from . import connection as api_connection
|
||||
from . import files as api_files
|
||||
from . import settings as api_settings
|
||||
from . import timelapse as api_timelapse
|
||||
|
|
|
|||
62
src/octoprint/server/api/connection.py
Normal file
62
src/octoprint/server/api/connection.py
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
# coding=utf-8
|
||||
__author__ = "Gina Häußge <osd@foosel.net>"
|
||||
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
|
||||
|
||||
from flask import request, jsonify, make_response
|
||||
|
||||
from octoprint.settings import settings
|
||||
from octoprint.printer import getConnectionOptions
|
||||
from octoprint.server import printer, restricted_access, NO_CONTENT
|
||||
from octoprint.server.api import api
|
||||
import octoprint.util as util
|
||||
|
||||
|
||||
@api.route("/connection", methods=["GET"])
|
||||
def connectionState():
|
||||
state, port, baudrate = printer.getCurrentConnection()
|
||||
current = {
|
||||
"state": state,
|
||||
"port": port,
|
||||
"baudrate": baudrate
|
||||
}
|
||||
return jsonify({"current": current, "options": getConnectionOptions()})
|
||||
|
||||
|
||||
@api.route("/connection", methods=["POST"])
|
||||
@restricted_access
|
||||
def connectionCommand():
|
||||
valid_commands = {
|
||||
"connect": ["autoconnect"],
|
||||
"disconnect": []
|
||||
}
|
||||
|
||||
command, data, response = util.getJsonCommandFromRequest(request, valid_commands)
|
||||
if response is not None:
|
||||
return response
|
||||
|
||||
if command == "connect":
|
||||
options = getConnectionOptions()
|
||||
|
||||
port = None
|
||||
baudrate = None
|
||||
if "port" in data.keys():
|
||||
port = data["port"]
|
||||
if port not in options["ports"]:
|
||||
return make_response("Invalid port: %s" % port, 400)
|
||||
if "baudrate" in data.keys():
|
||||
baudrate = data["baudrate"]
|
||||
if baudrate not in options["baudrates"]:
|
||||
return make_response("Invalid baudrate: %d" % baudrate, 400)
|
||||
if "save" in data.keys() and data["save"]:
|
||||
settings().set(["serial", "port"], port)
|
||||
settings().setInt(["serial", "baudrate"], baudrate)
|
||||
if "autoconnect" in data.keys():
|
||||
settings().setBoolean(["serial", "autoconnect"], data["autoconnect"])
|
||||
settings().save()
|
||||
printer.connect(port=port, baudrate=baudrate)
|
||||
elif command == "disconnect":
|
||||
printer.disconnect()
|
||||
|
||||
return NO_CONTENT
|
||||
|
||||
|
||||
|
|
@ -22,7 +22,7 @@ from octoprint.server.api import api
|
|||
def readGcodeFiles():
|
||||
files = _getFileList(FileDestinations.LOCAL)
|
||||
files.extend(_getFileList(FileDestinations.SDCARD))
|
||||
return jsonify(files=files, free=util.getFormattedSize(util.getFreeBytes(settings().getBaseFolder("uploads"))))
|
||||
return jsonify(files=files, free=util.getFreeBytes(settings().getBaseFolder("uploads")))
|
||||
|
||||
|
||||
@api.route("/files/<string:origin>", methods=["GET"])
|
||||
|
|
@ -33,7 +33,7 @@ def readGcodeFilesForOrigin(origin):
|
|||
files = _getFileList(origin)
|
||||
|
||||
if origin == FileDestinations.LOCAL:
|
||||
return jsonify(files=files, free=util.getFormattedSize(util.getFreeBytes(settings().getBaseFolder("uploads"))))
|
||||
return jsonify(files=files, free=util.getFreeBytes(settings().getBaseFolder("uploads")))
|
||||
else:
|
||||
return jsonify(files=files)
|
||||
|
||||
|
|
@ -64,7 +64,6 @@ def _getFileList(origin):
|
|||
files = gcodeManager.getAllFileData()
|
||||
for file in files:
|
||||
file.update({
|
||||
"origin": FileDestinations.LOCAL,
|
||||
"refs": {
|
||||
"resource": url_for(".readGcodeFile", target=FileDestinations.LOCAL, filename=file["name"], _external=True),
|
||||
"download": urlForDownload(FileDestinations.LOCAL, file["name"])
|
||||
|
|
|
|||
57
src/octoprint/server/api/job.py
Normal file
57
src/octoprint/server/api/job.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
# coding=utf-8
|
||||
__author__ = "Gina Häußge <osd@foosel.net>"
|
||||
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
|
||||
|
||||
from flask import request, make_response, jsonify
|
||||
|
||||
from octoprint.server import printer, restricted_access, NO_CONTENT
|
||||
from octoprint.server.api import api
|
||||
import octoprint.util as util
|
||||
|
||||
|
||||
@api.route("/job", methods=["POST"])
|
||||
@restricted_access
|
||||
def controlJob():
|
||||
if not printer.isOperational():
|
||||
return make_response("Printer is not operational", 409)
|
||||
|
||||
valid_commands = {
|
||||
"start": [],
|
||||
"restart": [],
|
||||
"pause": [],
|
||||
"cancel": []
|
||||
}
|
||||
|
||||
command, data, response = util.getJsonCommandFromRequest(request, valid_commands)
|
||||
if response is not None:
|
||||
return response
|
||||
|
||||
activePrintjob = printer.isPrinting() or printer.isPaused()
|
||||
|
||||
if command == "start":
|
||||
if activePrintjob:
|
||||
return make_response("Printer already has an active print job, did you mean 'restart'?", 409)
|
||||
printer.startPrint()
|
||||
elif command == "restart":
|
||||
if not printer.isPaused():
|
||||
return make_response("Printer does not have an active print job or is not paused", 409)
|
||||
printer.startPrint()
|
||||
elif command == "pause":
|
||||
if not activePrintjob:
|
||||
return make_response("Printer is neither printing nor paused, 'pause' command cannot be performed", 409)
|
||||
printer.togglePausePrint()
|
||||
elif command == "cancel":
|
||||
if not activePrintjob:
|
||||
return make_response("Printer is neither printing nor paused, 'cancel' command cannot be performed", 409)
|
||||
printer.cancelPrint()
|
||||
return NO_CONTENT
|
||||
|
||||
|
||||
@api.route("/job", methods=["GET"])
|
||||
def jobState():
|
||||
currentData = printer.getCurrentData()
|
||||
return jsonify({
|
||||
"job": currentData["job"],
|
||||
"progress": currentData["progress"],
|
||||
"state": currentData["state"]["stateString"]
|
||||
})
|
||||
|
|
@ -11,128 +11,10 @@ from octoprint.server.api import api
|
|||
import octoprint.util as util
|
||||
|
||||
|
||||
#~~ Printer control
|
||||
#~~ Heater
|
||||
|
||||
|
||||
@api.route("/control/connection", methods=["GET"])
|
||||
def connectionState():
|
||||
state, port, baudrate = printer.getCurrentConnection()
|
||||
current = {
|
||||
"state": state,
|
||||
"port": port,
|
||||
"baudrate": baudrate
|
||||
}
|
||||
return jsonify({"current": current, "options": getConnectionOptions()})
|
||||
|
||||
|
||||
@api.route("/control/connection", methods=["POST"])
|
||||
@restricted_access
|
||||
def connectionCommand():
|
||||
valid_commands = {
|
||||
"connect": ["autoconnect"],
|
||||
"disconnect": []
|
||||
}
|
||||
|
||||
command, data, response = util.getJsonCommandFromRequest(request, valid_commands)
|
||||
if response is not None:
|
||||
return response
|
||||
|
||||
if command == "connect":
|
||||
options = getConnectionOptions()
|
||||
|
||||
port = None
|
||||
baudrate = None
|
||||
if "port" in data.keys():
|
||||
port = data["port"]
|
||||
if port not in options["ports"]:
|
||||
return make_response("Invalid port: %s" % port, 400)
|
||||
if "baudrate" in data.keys():
|
||||
baudrate = data["baudrate"]
|
||||
if baudrate not in options["baudrates"]:
|
||||
return make_response("Invalid baudrate: %d" % baudrate, 400)
|
||||
if "save" in data.keys() and data["save"]:
|
||||
settings().set(["serial", "port"], port)
|
||||
settings().setInt(["serial", "baudrate"], baudrate)
|
||||
if "autoconnect" in data.keys():
|
||||
settings().setBoolean(["serial", "autoconnect"], data["autoconnect"])
|
||||
settings().save()
|
||||
printer.connect(port=port, baudrate=baudrate)
|
||||
elif command == "disconnect":
|
||||
printer.disconnect()
|
||||
|
||||
return NO_CONTENT
|
||||
|
||||
|
||||
@api.route("/control/printer/command", methods=["POST"])
|
||||
@restricted_access
|
||||
def printerCommand():
|
||||
# TODO: document me
|
||||
if not printer.isOperational():
|
||||
return make_response("Printer is not operational", 409)
|
||||
|
||||
if not "application/json" in request.headers["Content-Type"]:
|
||||
return make_response("Expected content type JSON", 400)
|
||||
|
||||
data = request.json
|
||||
|
||||
parameters = {}
|
||||
if "parameters" in data.keys(): parameters = data["parameters"]
|
||||
|
||||
commands = []
|
||||
if "command" in data.keys(): commands = [data["command"]]
|
||||
elif "commands" in data.keys(): commands = data["commands"]
|
||||
|
||||
commandsToSend = []
|
||||
for command in commands:
|
||||
commandToSend = command
|
||||
if len(parameters) > 0:
|
||||
commandToSend = command % parameters
|
||||
commandsToSend.append(commandToSend)
|
||||
|
||||
printer.commands(commandsToSend)
|
||||
|
||||
return NO_CONTENT
|
||||
|
||||
|
||||
@api.route("/control/job", methods=["POST"])
|
||||
@restricted_access
|
||||
def controlJob():
|
||||
if not printer.isOperational():
|
||||
return make_response("Printer is not operational", 409)
|
||||
|
||||
valid_commands = {
|
||||
"start": [],
|
||||
"restart": [],
|
||||
"pause": [],
|
||||
"cancel": []
|
||||
}
|
||||
|
||||
command, data, response = util.getJsonCommandFromRequest(request, valid_commands)
|
||||
if response is not None:
|
||||
return response
|
||||
|
||||
activePrintjob = printer.isPrinting() or printer.isPaused()
|
||||
|
||||
if command == "start":
|
||||
if activePrintjob:
|
||||
return make_response("Printer already has an active print job, did you mean 'restart'?", 409)
|
||||
printer.startPrint()
|
||||
elif command == "restart":
|
||||
if not printer.isPaused():
|
||||
return make_response("Printer does not have an active print job or is not paused", 409)
|
||||
printer.startPrint()
|
||||
elif command == "pause":
|
||||
if not activePrintjob:
|
||||
return make_response("Printer is neither printing nor paused, 'pause' command cannot be performed", 409)
|
||||
printer.togglePausePrint()
|
||||
elif command == "cancel":
|
||||
if not activePrintjob:
|
||||
return make_response("Printer is neither printing nor paused, 'cancel' command cannot be performed", 409)
|
||||
printer.cancelPrint()
|
||||
return NO_CONTENT
|
||||
|
||||
|
||||
@api.route("/control/printer/heater", methods=["POST"])
|
||||
@api.route("/printer/heater", methods=["POST"])
|
||||
@restricted_access
|
||||
def controlPrinterHotend():
|
||||
if not printer.isOperational():
|
||||
|
|
@ -194,7 +76,10 @@ def controlPrinterHotend():
|
|||
return NO_CONTENT
|
||||
|
||||
|
||||
@api.route("/control/printer/printhead", methods=["POST"])
|
||||
##~~ Print head
|
||||
|
||||
|
||||
@api.route("/printer/printhead", methods=["POST"])
|
||||
@restricted_access
|
||||
def controlPrinterPrinthead():
|
||||
if not printer.isOperational() or printer.isPrinting():
|
||||
|
|
@ -244,7 +129,10 @@ def controlPrinterPrinthead():
|
|||
return NO_CONTENT
|
||||
|
||||
|
||||
@api.route("/control/printer/feeder", methods=["POST"])
|
||||
##~~ Feeder
|
||||
|
||||
|
||||
@api.route("/printer/feeder", methods=["POST"])
|
||||
@restricted_access
|
||||
def controlPrinterFeeder():
|
||||
if not printer.isOperational() or printer.isPrinting():
|
||||
|
|
@ -270,14 +158,11 @@ def controlPrinterFeeder():
|
|||
|
||||
return NO_CONTENT
|
||||
|
||||
@api.route("/control/custom", methods=["GET"])
|
||||
def getCustomControls():
|
||||
# TODO: document me
|
||||
customControls = settings().get(["controls"])
|
||||
return jsonify(controls=customControls)
|
||||
|
||||
##~~ SD Card
|
||||
|
||||
|
||||
@api.route("/control/printer/sd", methods=["POST"])
|
||||
@api.route("/printer/sd", methods=["POST"])
|
||||
@restricted_access
|
||||
def sdCommand():
|
||||
if not settings().getBoolean(["feature", "sdSupport"]):
|
||||
|
|
@ -304,10 +189,52 @@ def sdCommand():
|
|||
|
||||
return NO_CONTENT
|
||||
|
||||
@api.route("/control/printer/sd", methods=["GET"])
|
||||
@api.route("/printer/sd", methods=["GET"])
|
||||
def sdState():
|
||||
if not settings().getBoolean(["feature", "sdSupport"]):
|
||||
return make_response("SD support is disabled", 404)
|
||||
|
||||
return jsonify(ready=printer.isSdReady())
|
||||
|
||||
|
||||
##~~ Commands
|
||||
|
||||
|
||||
@api.route("/printer/command", methods=["POST"])
|
||||
@restricted_access
|
||||
def printerCommand():
|
||||
# TODO: document me
|
||||
if not printer.isOperational():
|
||||
return make_response("Printer is not operational", 409)
|
||||
|
||||
if not "application/json" in request.headers["Content-Type"]:
|
||||
return make_response("Expected content type JSON", 400)
|
||||
|
||||
data = request.json
|
||||
|
||||
parameters = {}
|
||||
if "parameters" in data.keys(): parameters = data["parameters"]
|
||||
|
||||
commands = []
|
||||
if "command" in data.keys(): commands = [data["command"]]
|
||||
elif "commands" in data.keys(): commands = data["commands"]
|
||||
|
||||
commandsToSend = []
|
||||
for command in commands:
|
||||
commandToSend = command
|
||||
if len(parameters) > 0:
|
||||
commandToSend = command % parameters
|
||||
commandsToSend.append(commandToSend)
|
||||
|
||||
printer.commands(commandsToSend)
|
||||
|
||||
return NO_CONTENT
|
||||
|
||||
|
||||
@api.route("/printer/command/custom", methods=["GET"])
|
||||
def getCustomControls():
|
||||
# TODO: document me
|
||||
customControls = settings().get(["controls"])
|
||||
return jsonify(controls=customControls)
|
||||
|
||||
|
||||
|
|
@ -277,3 +277,34 @@ function ItemListHelper(listType, supportedSorting, supportedFilters, defaultSor
|
|||
self._loadCurrentFiltersFromLocalStorage();
|
||||
self._loadCurrentSortingFromLocalStorage();
|
||||
}
|
||||
|
||||
function formatSize(bytes) {
|
||||
var units = ["bytes", "KB", "MB", "GB"];
|
||||
for (var i = 0; i < units.length; i++) {
|
||||
if (bytes < 1024) {
|
||||
return _.sprintf("%3.1f%s", bytes, units[i]);
|
||||
}
|
||||
bytes /= 1024;
|
||||
}
|
||||
return _.sprintf("%.1f%s", bytes, "TB");
|
||||
}
|
||||
|
||||
function formatDuration(seconds) {
|
||||
var s = seconds % 60;
|
||||
var m = (seconds % 3600) / 60;
|
||||
var h = seconds / 3600;
|
||||
|
||||
return _.sprintf("%02d:%02d:%02d", h, m, s);
|
||||
}
|
||||
|
||||
function formatDate(unixTimestamp) {
|
||||
return moment.unix(unixTimestamp).format("YYYY-MM-DD HH:mm");
|
||||
}
|
||||
|
||||
function formatFilament(filament) {
|
||||
var result = _.sprintf("%.02fm", (filament["length"] / 1000));
|
||||
if (filament.hasOwnProperty("volume")) {
|
||||
result += " / " + _.sprintf("%.02fcm³", filament["volume"]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -264,6 +264,10 @@ $(function() {
|
|||
//~~ Offline overlay
|
||||
$("#offline_overlay_reconnect").click(function() {dataUpdater.reconnect()});
|
||||
|
||||
//~~ Underscore setup
|
||||
|
||||
_.mixin(_.str.exports());
|
||||
|
||||
//~~ knockout.js bindings
|
||||
|
||||
ko.bindingHandlers.popover = {
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ function ConnectionViewModel(loginStateViewModel, settingsViewModel) {
|
|||
|
||||
self.requestData = function() {
|
||||
$.ajax({
|
||||
url: API_BASEURL + "control/connection",
|
||||
url: API_BASEURL + "connection",
|
||||
method: "GET",
|
||||
dataType: "json",
|
||||
success: function(response) {
|
||||
|
|
@ -100,7 +100,7 @@ function ConnectionViewModel(loginStateViewModel, settingsViewModel) {
|
|||
data["save"] = true;
|
||||
|
||||
$.ajax({
|
||||
url: API_BASEURL + "control/connection",
|
||||
url: API_BASEURL + "connection",
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: "application/json; charset=UTF-8",
|
||||
|
|
@ -112,7 +112,7 @@ function ConnectionViewModel(loginStateViewModel, settingsViewModel) {
|
|||
} else {
|
||||
self.requestData();
|
||||
$.ajax({
|
||||
url: API_BASEURL + "control/connection",
|
||||
url: API_BASEURL + "connection",
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: "application/json; charset=UTF-8",
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ function ControlViewModel(loginStateViewModel, settingsViewModel) {
|
|||
|
||||
self.requestData = function() {
|
||||
$.ajax({
|
||||
url: API_BASEURL + "control/custom",
|
||||
url: API_BASEURL + "printer/command/custom",
|
||||
method: "GET",
|
||||
dataType: "json",
|
||||
success: function(response) {
|
||||
|
|
@ -90,7 +90,7 @@ function ControlViewModel(loginStateViewModel, settingsViewModel) {
|
|||
data[axis] = distance * multiplier;
|
||||
|
||||
$.ajax({
|
||||
url: API_BASEURL + "control/printer/printhead",
|
||||
url: API_BASEURL + "printer/printhead",
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: "application/json; charset=UTF-8",
|
||||
|
|
@ -105,7 +105,7 @@ function ControlViewModel(loginStateViewModel, settingsViewModel) {
|
|||
}
|
||||
|
||||
$.ajax({
|
||||
url: API_BASEURL + "control/printer/printhead",
|
||||
url: API_BASEURL + "printer/printhead",
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: "application/json; charset=UTF-8",
|
||||
|
|
@ -127,7 +127,7 @@ function ControlViewModel(loginStateViewModel, settingsViewModel) {
|
|||
length = 5;
|
||||
|
||||
$.ajax({
|
||||
url: API_BASEURL + "control/printer/feeder",
|
||||
url: API_BASEURL + "printer/feeder",
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: "application/json; charset=UTF-8",
|
||||
|
|
@ -160,7 +160,7 @@ function ControlViewModel(loginStateViewModel, settingsViewModel) {
|
|||
return;
|
||||
|
||||
$.ajax({
|
||||
url: API_BASEURL + "control/printer/command",
|
||||
url: API_BASEURL + "printer/command",
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: "application/json; charset=UTF-8",
|
||||
|
|
|
|||
|
|
@ -14,6 +14,11 @@ function GcodeFilesViewModel(printerStateViewModel, loginStateViewModel) {
|
|||
self.isSdReady = ko.observable(undefined);
|
||||
|
||||
self.freeSpace = ko.observable(undefined);
|
||||
self.freeSpaceString = ko.computed(function() {
|
||||
if (!self.freeSpace())
|
||||
return "-";
|
||||
return formatSize(self.freeSpace());
|
||||
});
|
||||
|
||||
// initialize list helper
|
||||
self.listHelper = new ItemListHelper(
|
||||
|
|
@ -174,7 +179,7 @@ function GcodeFilesViewModel(printerStateViewModel, loginStateViewModel) {
|
|||
|
||||
self._sendSdCommand = function(command) {
|
||||
$.ajax({
|
||||
url: API_BASEURL + "control/printer/sd",
|
||||
url: API_BASEURL + "printer/sd",
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: "application/json; charset=UTF-8",
|
||||
|
|
@ -183,16 +188,18 @@ function GcodeFilesViewModel(printerStateViewModel, loginStateViewModel) {
|
|||
}
|
||||
|
||||
self.getPopoverContent = function(data) {
|
||||
var output = "<p><strong>Uploaded:</strong> " + data["date"] + "</p>";
|
||||
var output = "<p><strong>Uploaded:</strong> " + formatDate(data["date"]) + "</p>";
|
||||
if (data["gcodeAnalysis"]) {
|
||||
output += "<p>";
|
||||
output += "<strong>Filament:</strong> " + data["gcodeAnalysis"]["filament"] + "<br>";
|
||||
output += "<strong>Estimated Print Time:</strong> " + data["gcodeAnalysis"]["estimatedPrintTime"];
|
||||
if (data["gcodeAnalysis"]["filament"]) {
|
||||
output += "<strong>Filament:</strong> " + formatFilament(data["gcodeAnalysis"]["filament"]) + "<br>";
|
||||
}
|
||||
output += "<strong>Estimated Print Time:</strong> " + formatDuration(data["gcodeAnalysis"]["estimatedPrintTime"]);
|
||||
output += "</p>";
|
||||
}
|
||||
if (data["prints"] && data["prints"]["last"]) {
|
||||
output += "<p>";
|
||||
output += "<strong>Last Print:</strong> <span class=\"" + (data["prints"]["last"]["success"] ? "text-success" : "text-error") + "\">" + data["prints"]["last"]["date"] + "</span>";
|
||||
output += "<strong>Last Print:</strong> <span class=\"" + (data["prints"]["last"]["success"] ? "text-success" : "text-error") + "\">" + formatDate(data["prints"]["last"]["date"]) + "</span>";
|
||||
output += "</p>";
|
||||
}
|
||||
return output;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ function GcodeViewModel(loginStateViewModel) {
|
|||
self.loginState = loginStateViewModel;
|
||||
|
||||
self.loadedFilename = undefined;
|
||||
self.loadedFileMTime = undefined;
|
||||
self.loadedFileDate = undefined;
|
||||
self.status = 'idle';
|
||||
self.enabled = false;
|
||||
|
||||
|
|
@ -15,18 +15,18 @@ function GcodeViewModel(loginStateViewModel) {
|
|||
GCODE.ui.initHandlers();
|
||||
}
|
||||
|
||||
self.loadFile = function(filename, mtime){
|
||||
self.loadFile = function(filename, date){
|
||||
if (self.status == 'idle' && self.errorCount < 3) {
|
||||
self.status = 'request';
|
||||
$.ajax({
|
||||
url: BASEURL + "downloads/files/local/" + filename,
|
||||
data: { "mtime": mtime },
|
||||
data: { "ctime": date },
|
||||
type: "GET",
|
||||
success: function(response, rstatus) {
|
||||
if(rstatus === 'success'){
|
||||
self.showGCodeViewer(response, rstatus);
|
||||
self.loadedFilename=filename;
|
||||
self.loadedFileMTime=mtime;
|
||||
self.loadedFilename = filename;
|
||||
self.loadedFileDate = date;
|
||||
self.status = 'idle';
|
||||
}
|
||||
},
|
||||
|
|
@ -55,20 +55,20 @@ function GcodeViewModel(loginStateViewModel) {
|
|||
|
||||
self._processData = function(data) {
|
||||
if (!self.enabled) return;
|
||||
if (!data.job.filename) return;
|
||||
if (!data.job.file || !data.job.file.name) return;
|
||||
|
||||
if(self.loadedFilename && self.loadedFilename == data.job.filename &&
|
||||
self.loadedFileMTime == data.job.mtime) {
|
||||
if(self.loadedFilename && self.loadedFilename == data.job.file.name &&
|
||||
self.loadedFileDate == data.job.file.date) {
|
||||
if (data.state.flags && (data.state.flags.printing || data.state.flags.paused)) {
|
||||
var cmdIndex = GCODE.gCodeReader.getCmdIndexForPercentage(data.progress.progress * 100);
|
||||
var cmdIndex = GCODE.gCodeReader.getCmdIndexForPercentage(data.progress.completion);
|
||||
if(cmdIndex){
|
||||
GCODE.renderer.render(cmdIndex.layer, 0, cmdIndex.cmd);
|
||||
GCODE.ui.updateLayerInfo(cmdIndex.layer);
|
||||
}
|
||||
}
|
||||
self.errorCount = 0
|
||||
} else if (data.job.filename && !data.job.sd) {
|
||||
self.loadFile(data.job.filename, data.job.mtime);
|
||||
} else if (data.job.file.name && data.job.file.origin != "sdcard") {
|
||||
self.loadFile(data.job.file.name, data.job.file.date);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,16 +27,36 @@ function PrinterStateViewModel(loginStateViewModel) {
|
|||
|
||||
self.currentHeight = ko.observable(undefined);
|
||||
|
||||
self.estimatedPrintTimeString = ko.computed(function() {
|
||||
if (!self.estimatedPrintTime())
|
||||
return "-";
|
||||
return formatDuration(self.estimatedPrintTime());
|
||||
});
|
||||
self.filamentString = ko.computed(function() {
|
||||
if (!self.filament())
|
||||
return "-";
|
||||
return formatFilament(self.filament());
|
||||
});
|
||||
self.byteString = ko.computed(function() {
|
||||
if (!self.filesize())
|
||||
return "-";
|
||||
var filepos = self.filepos() ? self.filepos() : "-";
|
||||
return filepos + " / " + self.filesize();
|
||||
var filepos = self.filepos() ? formatSize(self.filepos()) : "-";
|
||||
return filepos + " / " + formatSize(self.filesize());
|
||||
});
|
||||
self.heightString = ko.computed(function() {
|
||||
if (!self.currentHeight())
|
||||
return "-";
|
||||
return self.currentHeight();
|
||||
return _.sprintf("%.02fmm", self.currentHeight());
|
||||
});
|
||||
self.printTimeString = ko.computed(function() {
|
||||
if (!self.printTime())
|
||||
return "-";
|
||||
return formatDuration(self.printTime());
|
||||
});
|
||||
self.printTimeLeftString = ko.computed(function() {
|
||||
if (!self.printTimeLeft())
|
||||
return "-";
|
||||
return formatDuration(self.printTimeLeft());
|
||||
})
|
||||
self.progressString = ko.computed(function() {
|
||||
if (!self.progress())
|
||||
|
|
@ -97,16 +117,22 @@ function PrinterStateViewModel(loginStateViewModel) {
|
|||
}
|
||||
|
||||
self._processJobData = function(data) {
|
||||
self.filename(data.filename);
|
||||
self.filesize(data.filesize);
|
||||
if (data.file) {
|
||||
self.filename(data.file.name);
|
||||
self.filesize(data.file.size);
|
||||
self.sd(data.file.origin == "sdcard");
|
||||
} else {
|
||||
self.filename(undefined);
|
||||
self.filesize(undefined);
|
||||
self.sd(undefined);
|
||||
}
|
||||
self.estimatedPrintTime(data.estimatedPrintTime);
|
||||
self.filament(data.filament);
|
||||
self.sd(data.sd);
|
||||
}
|
||||
|
||||
self._processProgressData = function(data) {
|
||||
if (data.progress) {
|
||||
self.progress(Math.round(data.progress * 100));
|
||||
if (data.completion) {
|
||||
self.progress(data.completion);
|
||||
} else {
|
||||
self.progress(undefined);
|
||||
}
|
||||
|
|
@ -145,7 +171,7 @@ function PrinterStateViewModel(loginStateViewModel) {
|
|||
|
||||
self._jobCommand = function(command) {
|
||||
$.ajax({
|
||||
url: API_BASEURL + "control/job",
|
||||
url: API_BASEURL + "job",
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: "application/json; charset=UTF-8",
|
||||
|
|
|
|||
|
|
@ -126,10 +126,10 @@ function TemperatureViewModel(loginStateViewModel, settingsViewModel) {
|
|||
|
||||
_.each(data, function(d) {
|
||||
var time = d.currentTime;
|
||||
self.temperatures.actual.push([time, d.temp]);
|
||||
self.temperatures.target.push([time, d.targetTemp]);
|
||||
self.temperatures.actualBed.push([time, d.bedTemp]);
|
||||
self.temperatures.targetBed.push([time, d.targetBedTemp]);
|
||||
self.temperatures.actual.push([time * 1000, d.temp]);
|
||||
self.temperatures.target.push([time * 1000, d.targetTemp]);
|
||||
self.temperatures.actualBed.push([time * 1000, d.bedTemp]);
|
||||
self.temperatures.targetBed.push([time * 1000, d.targetBedTemp]);
|
||||
});
|
||||
|
||||
self.temperatures.actual = self.temperatures.actual.slice(-300);
|
||||
|
|
@ -141,7 +141,17 @@ function TemperatureViewModel(loginStateViewModel, settingsViewModel) {
|
|||
}
|
||||
|
||||
self._processTemperatureHistoryData = function(data) {
|
||||
self.temperatures = data;
|
||||
var toJsTimestamp = function(d) {
|
||||
return [d[0] * 1000, d[1]];
|
||||
}
|
||||
|
||||
var processedData = {
|
||||
actual: _.map(data.actual, toJsTimestamp),
|
||||
target: _.map(data.target, toJsTimestamp),
|
||||
actualBed: _.map(data.actualBed, toJsTimestamp),
|
||||
targetBed: _.map(data.targetBed, toJsTimestamp)
|
||||
};
|
||||
self.temperatures = processedData;
|
||||
self.updatePlot();
|
||||
}
|
||||
|
||||
|
|
@ -217,7 +227,7 @@ function TemperatureViewModel(loginStateViewModel, settingsViewModel) {
|
|||
data[group][type] = parseInt(temp);
|
||||
|
||||
$.ajax({
|
||||
url: API_BASEURL + "control/printer/heater",
|
||||
url: API_BASEURL + "printer/heater",
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: "application/json; charset=UTF-8",
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ function TerminalViewModel(loginStateViewModel, settingsViewModel) {
|
|||
|
||||
if (command) {
|
||||
$.ajax({
|
||||
url: API_BASEURL + "control/printer/command",
|
||||
url: API_BASEURL + "printer/command",
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: "application/json; charset=UTF-8",
|
||||
|
|
|
|||
6
src/octoprint/static/js/lib/moment.min.js
vendored
Normal file
6
src/octoprint/static/js/lib/moment.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
6
src/octoprint/static/js/lib/underscore-min.js
vendored
Normal file
6
src/octoprint/static/js/lib/underscore-min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
src/octoprint/static/js/lib/underscore.string.min.js
vendored
Normal file
1
src/octoprint/static/js/lib/underscore.string.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -115,12 +115,12 @@
|
|||
<div class="accordion-inner">
|
||||
Machine State: <strong data-bind="text: stateString"></strong><br>
|
||||
File: <strong data-bind="text: filename"></strong> <strong data-bind="visible: sd">(SD)</strong><br>
|
||||
Filament: <strong data-bind="text: filament"></strong><br>
|
||||
Estimated Print Time: <strong data-bind="text: estimatedPrintTime"></strong><br>
|
||||
Filament: <strong data-bind="text: filamentString"></strong><br>
|
||||
Estimated Print Time: <strong data-bind="text: estimatedPrintTimeString"></strong><br>
|
||||
Timelapse: <strong data-bind="text: timelapseString"></strong><br>
|
||||
Height: <strong data-bind="text: heightString"></strong><br>
|
||||
Print Time: <strong data-bind="text: printTime"></strong><br>
|
||||
Print Time Left: <strong data-bind="text: printTimeLeft"></strong><br>
|
||||
Print Time: <strong data-bind="text: printTimeString"></strong><br>
|
||||
Print Time Left: <strong data-bind="text: printTimeLeftString"></strong><br>
|
||||
Printed: <strong data-bind="text: byteString"></strong><br>
|
||||
|
||||
<div class="progress">
|
||||
|
|
@ -183,7 +183,7 @@
|
|||
<tbody data-bind="foreach: listHelper.paginatedItems">
|
||||
<tr data-bind="css: $root.getSuccessClass($data), style: { 'font-weight': $root.listHelper.isSelected($data) ? 'bold' : 'normal' }, popover: { title: name, animation: true, html: true, placement: 'right', trigger: 'hover', delay: 0, content: $root.getPopoverContent($data), html: true }">
|
||||
<td class="gcode_files_name" data-bind="text: name"></td>
|
||||
<td class="gcode_files_size" data-bind="text: size"></td>
|
||||
<td class="gcode_files_size" data-bind="text: formatSize(size)"></td>
|
||||
<td class="gcode_files_action">
|
||||
<a href="#" class="icon-trash" title="Remove" data-bind="click: function() { if ($root.enableRemove($data)) { $root.removeFile($data.name); } else { return; } }, css: {disabled: !$root.enableRemove($data)}"></a> | <a href="#" class="icon-folder-open" title="Load" data-bind="click: function() { if ($root.enableSelect($data)) { $root.loadFile($data.name, false); } else { return; } }, css: {disabled: !$root.enableSelect($data)}"></a> | <a href="#" class="icon-print" title="Load and Print" data-bind="click: function() { if ($root.enableSelect($data)) { $root.loadFile($data.name, true); } else { return; } }, css: {disabled: !$root.enableSelect($data)}"></a>
|
||||
</td>
|
||||
|
|
@ -191,7 +191,7 @@
|
|||
</tbody>
|
||||
</table>
|
||||
<div class="muted text-right">
|
||||
<small>Free: <span data-bind="text: freeSpace"></span></small>
|
||||
<small>Free: <span data-bind="text: freeSpaceString"></span></small>
|
||||
</div>
|
||||
<div class="pagination pagination-mini pagination-centered">
|
||||
<ul>
|
||||
|
|
@ -633,7 +633,8 @@
|
|||
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/jquery/jquery.min.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/modernizr.custom.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/underscore.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/underscore-min.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/underscore.string.min.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/knockout.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/avltree.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/bootstrap/bootstrap.js') }}"></script>
|
||||
|
|
@ -648,6 +649,7 @@
|
|||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/jquery/jquery.iframe-transport.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/jquery/jquery.fileupload.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/sockjs-0.3.4.min.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/moment.min.js') }}"></script>
|
||||
|
||||
<!-- Include OctoPrint files -->
|
||||
<!-- TODO: merge/minimize in the future -->
|
||||
|
|
|
|||
Loading…
Reference in a new issue