Add: universal task display
This commit is contained in:
@@ -5,7 +5,9 @@ set(src_files
|
|||||||
src/api.cpp
|
src/api.cpp
|
||||||
src/worker.hpp
|
src/worker.hpp
|
||||||
src/worker.cpp
|
src/worker.cpp
|
||||||
src/tables.hpp)
|
src/tables.hpp
|
||||||
|
src/file_status.hpp
|
||||||
|
src/file_status.cpp)
|
||||||
|
|
||||||
|
|
||||||
add_executable(localTubeServer ${src_files})
|
add_executable(localTubeServer ${src_files})
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <exception>
|
||||||
|
|
||||||
#include <soci/boost-optional.h>
|
#include <soci/boost-optional.h>
|
||||||
|
|
||||||
@@ -16,6 +17,8 @@
|
|||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#include "file_status.hpp"
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
Api::Api(soci::session& sql, httplib::Server& server) : sql(sql)
|
Api::Api(soci::session& sql, httplib::Server& server) : sql(sql)
|
||||||
@@ -116,53 +119,10 @@ void Api::files(httplib::Request const& rq, httplib::Response& rs)
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
auto data = TaskInfo::getAll(sql);
|
||||||
|
|
||||||
json res;
|
json res;
|
||||||
|
res["files"] = data;
|
||||||
// Query incomplete Tasks
|
|
||||||
|
|
||||||
soci::rowset<Task> data = (sql.prepare << "SELECT * "
|
|
||||||
"FROM Tasks "
|
|
||||||
"WHERE status!=2 "
|
|
||||||
"ORDER BY id DESC ");
|
|
||||||
|
|
||||||
for (auto const& el : data)
|
|
||||||
{
|
|
||||||
json current;
|
|
||||||
current["hash"] = el.hash;
|
|
||||||
current["status"] = magic_enum::enum_name(el.status);
|
|
||||||
current["status_id"] = static_cast<int>(el.status);
|
|
||||||
current["source"] = el.url;
|
|
||||||
current["name"] = fmt::format("File {}", el.id);
|
|
||||||
current["audio_only"] = el.audio_only;
|
|
||||||
current["format"] = el.format.value_or("Source");
|
|
||||||
|
|
||||||
res["queue"].push_back(current);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Downloaded
|
|
||||||
|
|
||||||
soci::rowset<soci::row> files =
|
|
||||||
(sql.prepare
|
|
||||||
<< R"(SELECT Tasks.id, Files.timestamp, Files.hash, audio_only, filename, Files.format
|
|
||||||
FROM Files,
|
|
||||||
Tasks
|
|
||||||
WHERE Tasks.hash == Files.hash
|
|
||||||
ORDER BY Files.timestamp DESC )");
|
|
||||||
|
|
||||||
for (auto const& el : files)
|
|
||||||
{
|
|
||||||
json current;
|
|
||||||
|
|
||||||
std::time_t time = el.get<int>("timestamp");
|
|
||||||
|
|
||||||
current["timestamp"] = fmt::format("{:%Y-%m-%d %H:%M:%S}", *std::localtime(&time));
|
|
||||||
current["hash"] = el.get<std::string>("hash");
|
|
||||||
current["audio_only"] = el.get<int>("audio_only");
|
|
||||||
current["filename"] = el.get<std::string>("filename");
|
|
||||||
current["format"] = el.get<std::string>("format");
|
|
||||||
|
|
||||||
res["files"].push_back(current);
|
|
||||||
}
|
|
||||||
|
|
||||||
rs.set_content(res.dump(), "application/json");
|
rs.set_content(res.dump(), "application/json");
|
||||||
}
|
}
|
||||||
@@ -191,14 +151,12 @@ void Api::file(const httplib::Request& rq, httplib::Response& rs)
|
|||||||
sql << fmt::format("SELECT * FROM Files WHERE hash='{}';", file), soci::into(dw);
|
sql << fmt::format("SELECT * FROM Files WHERE hash='{}';", file), soci::into(dw);
|
||||||
|
|
||||||
|
|
||||||
auto fs = std::make_shared<std::ifstream>(dw.local_path, std::ios_base::binary | std::ios::in);
|
auto fs =
|
||||||
|
std::make_shared<std::ifstream>(dw.local_path, std::ios_base::binary | std::ios::in);
|
||||||
fs->seekg(0, std::ios_base::end);
|
fs->seekg(0, std::ios_base::end);
|
||||||
auto size = fs->tellg();
|
auto size = fs->tellg();
|
||||||
fs->seekg(0);
|
fs->seekg(0);
|
||||||
|
|
||||||
// rs.body.resize(static_cast<size_t>(size));
|
|
||||||
// fs.read(&rs.body[0], static_cast<std::streamsize>(size));
|
|
||||||
|
|
||||||
rs.set_header("Content-Disposition", fmt::format("attachment; filename={};", dw.filename));
|
rs.set_header("Content-Disposition", fmt::format("attachment; filename={};", dw.filename));
|
||||||
|
|
||||||
rs.set_content_provider(
|
rs.set_content_provider(
|
||||||
@@ -207,7 +165,6 @@ void Api::file(const httplib::Request& rq, httplib::Response& rs)
|
|||||||
[file = fs](size_t offset, size_t length, httplib::DataSink& sink) mutable {
|
[file = fs](size_t offset, size_t length, httplib::DataSink& sink) mutable {
|
||||||
size_t size = std::min(length, (size_t)1024 * 500);
|
size_t size = std::min(length, (size_t)1024 * 500);
|
||||||
|
|
||||||
//spdlog::info("Stream offset {} length {}", offset, length);
|
|
||||||
std::vector<char> data;
|
std::vector<char> data;
|
||||||
data.resize(size);
|
data.resize(size);
|
||||||
|
|
||||||
@@ -237,55 +194,27 @@ void Api::status(httplib::Request const& rq, httplib::Response& rs)
|
|||||||
|
|
||||||
std::string hash = rq.matches[1];
|
std::string hash = rq.matches[1];
|
||||||
|
|
||||||
boost::optional<Task> data;
|
|
||||||
|
|
||||||
sql << "SELECT * FROM Tasks WHERE hash=:hash", soci::use(hash, "hash"), soci::into(data);
|
|
||||||
|
|
||||||
/*soci::rowset<Task> data = (sql.prepare << "SELECT * "
|
|
||||||
"FROM Tasks "
|
|
||||||
"WHERE hash=:hash ",
|
|
||||||
soci::use(hash, "hash"));*/
|
|
||||||
json response;
|
json response;
|
||||||
|
try
|
||||||
auto& status = response["status"];
|
{
|
||||||
|
auto data = TaskInfo::getId(sql, hash);
|
||||||
|
|
||||||
if (data)
|
if (data)
|
||||||
{
|
{
|
||||||
auto const& el = data.value();
|
response["status"] = data.value();
|
||||||
|
|
||||||
status["name"] = fmt::format("Task {}", el.hash);
|
|
||||||
status["source"] = el.url;
|
|
||||||
status["status"] = magic_enum::enum_name(el.status);
|
|
||||||
status["status_id"] = static_cast<int>(el.status);
|
|
||||||
status["timestamp"] = el.timestamp;
|
|
||||||
status["task_file_type"] =
|
|
||||||
fmt::format("{}: {}", el.audio_only ? "Audio" : "Video", el.format.value_or("Auto"));
|
|
||||||
status["id"] = el.hash;
|
|
||||||
|
|
||||||
if (el.status == FileStatus::COMPLETE)
|
|
||||||
{
|
|
||||||
boost::optional<File> file_data;
|
|
||||||
sql << "SELECT * FROM Files WHERE hash=:hash", soci::use(hash, "hash"),
|
|
||||||
soci::into(file_data);
|
|
||||||
|
|
||||||
if (file_data)
|
|
||||||
{
|
|
||||||
auto const& file = file_data.value();
|
|
||||||
|
|
||||||
auto& file_status = status["file"];
|
|
||||||
|
|
||||||
file_status["timestamp"] = file.timestamp;
|
|
||||||
file_status["name"] = file.filename;
|
|
||||||
file_status["format"] =
|
|
||||||
fmt::format("{}: {}", el.audio_only ? "Audio" : "Video", file.format);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else
|
} else
|
||||||
{
|
{
|
||||||
rs.status = 404;
|
rs.status = 404;
|
||||||
response["error"] = fmt::format("File '{}' not found!", hash);
|
response["error"] = fmt::format("File '{}' not found!", hash);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
catch (std::exception const& e)
|
||||||
|
{
|
||||||
|
spdlog::error("[Api Status] Excepion: {}", e.what());
|
||||||
|
rs.status = 404;
|
||||||
|
response["error"] = fmt::format("Exception: {}", e.what());
|
||||||
|
}
|
||||||
rs.set_content(response.dump(), "application/json");
|
rs.set_content(response.dump(), "application/json");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
76
Modules/Server/src/file_status.cpp
Normal file
76
Modules/Server/src/file_status.cpp
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
#include "file_status.hpp"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <soci/boost-optional.h>
|
||||||
|
|
||||||
|
#include "tables.hpp"
|
||||||
|
|
||||||
|
std::optional<TaskInfo> TaskInfo::getId(soci::session& sql, std::string_view hash)
|
||||||
|
{
|
||||||
|
auto hash_str = std::string { hash };
|
||||||
|
|
||||||
|
soci::rowset<soci::row> row_data = (sql.prepare << R"(--
|
||||||
|
SELECT Tasks.hash,
|
||||||
|
Tasks.status,
|
||||||
|
Tasks.timestamp AS TaskTimestamp,
|
||||||
|
Tasks.url,
|
||||||
|
Files.timestamp AS FileTimestamp,
|
||||||
|
Files.filename,
|
||||||
|
Files.format
|
||||||
|
FROM Tasks LEFT OUTER JOIN Files ON Files.id = Tasks.id
|
||||||
|
WHERE Tasks.hash = :file_hash ;)",
|
||||||
|
soci::use(hash_str, "file_hash"));
|
||||||
|
|
||||||
|
for (auto const& el : row_data)
|
||||||
|
{
|
||||||
|
return fromRow(el);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<TaskInfo> TaskInfo::getAll(soci::session& sql)
|
||||||
|
{
|
||||||
|
soci::rowset<soci::row> row_data(sql.prepare << R"(--
|
||||||
|
SELECT Tasks.hash,
|
||||||
|
Tasks.status,
|
||||||
|
Tasks.timestamp AS TaskTimestamp,
|
||||||
|
Tasks.url,
|
||||||
|
Files.timestamp AS FileTimestamp,
|
||||||
|
Files.filename,
|
||||||
|
Files.format
|
||||||
|
FROM Tasks LEFT JOIN Files ON Files.id = Tasks.id
|
||||||
|
ORDER BY TaskTimestamp DESC;")");
|
||||||
|
|
||||||
|
std::vector<TaskInfo> data;
|
||||||
|
|
||||||
|
for (auto const& el : row_data)
|
||||||
|
{
|
||||||
|
data.push_back(fromRow(el));
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskInfo TaskInfo::fromRow(soci::row const& row)
|
||||||
|
{
|
||||||
|
TaskInfo status;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
auto file_status = row.get<FileStatus>("status");
|
||||||
|
|
||||||
|
status.hash = row.get<std::string>("hash");
|
||||||
|
status.name = fmt::format("Task {}", status.hash);
|
||||||
|
status.status = magic_enum::enum_name(file_status);
|
||||||
|
status.timestamp = row.get<int>("TaskTimestamp");
|
||||||
|
status.url = row.get<std::string>("url");
|
||||||
|
|
||||||
|
if (file_status == FileStatus::COMPLETE)
|
||||||
|
{
|
||||||
|
status.file = { .timestamp = row.get<int>("FileTimestamp"),
|
||||||
|
.filename = row.get<std::string>("filename"),
|
||||||
|
.format = row.get<std::string>("format") };
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
46
Modules/Server/src/file_status.hpp
Normal file
46
Modules/Server/src/file_status.hpp
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <soci/session.h>
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
struct FileInfo
|
||||||
|
{
|
||||||
|
int64_t timestamp;
|
||||||
|
std::string filename;
|
||||||
|
std::string format;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TaskInfo
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
std::string hash;
|
||||||
|
std::string status;
|
||||||
|
std::string url;
|
||||||
|
int64_t timestamp;
|
||||||
|
std::optional<FileInfo> file;
|
||||||
|
|
||||||
|
static std::optional<TaskInfo> getId(soci::session& sql, std::string_view hash);
|
||||||
|
static std::vector<TaskInfo> getAll(soci::session& sql);
|
||||||
|
|
||||||
|
static TaskInfo fromRow(soci::row const& row);
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void to_json(json& j, const TaskInfo& p)
|
||||||
|
{
|
||||||
|
j = json { { "name", p.name },
|
||||||
|
{ "source", p.url },
|
||||||
|
{ "status", p.status },
|
||||||
|
{ "timestamp", p.timestamp },
|
||||||
|
{ "hash", p.hash } };
|
||||||
|
|
||||||
|
if(p.file)
|
||||||
|
{
|
||||||
|
j["file"]["timestamp"] = p.file.value().timestamp;
|
||||||
|
j["file"]["filename"] = p.file.value().filename;
|
||||||
|
j["file"]["format"] = p.file.value().format;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -65,7 +65,7 @@ int main()
|
|||||||
File::create_table(sql);
|
File::create_table(sql);
|
||||||
}
|
}
|
||||||
|
|
||||||
Worker worker(sql);
|
//Worker worker(sql);
|
||||||
|
|
||||||
httplib::Server srv;
|
httplib::Server srv;
|
||||||
srv.set_mount_point("/", "www/");
|
srv.set_mount_point("/", "www/");
|
||||||
@@ -75,5 +75,7 @@ int main()
|
|||||||
Api api(sql, srv);
|
Api api(sql, srv);
|
||||||
|
|
||||||
spdlog::info("Listing on 0.0.0.0 Port 80");
|
spdlog::info("Listing on 0.0.0.0 Port 80");
|
||||||
srv.listen("0.0.0.0", 80);
|
|
||||||
|
|
||||||
|
srv.listen("0.0.0.0", 8888);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,41 +6,27 @@ import Element.Font as Font
|
|||||||
import Element.Region as Region
|
import Element.Region as Region
|
||||||
import Globals exposing (apiFiles)
|
import Globals exposing (apiFiles)
|
||||||
import Http
|
import Http
|
||||||
import Json.Decode exposing (Decoder, int, list, string, succeed)
|
import Json.Decode exposing (Decoder, list, succeed)
|
||||||
import Json.Decode.Pipeline exposing (required)
|
import Json.Decode.Pipeline exposing (required)
|
||||||
|
import TaskStatus
|
||||||
|
|
||||||
|
|
||||||
type alias File =
|
|
||||||
{ name : String
|
|
||||||
, file_name : String
|
|
||||||
, source : String
|
|
||||||
, id : Int
|
|
||||||
, status : String
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
type alias FilesResponse =
|
|
||||||
{ queue : List File }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- Model --
|
-- Model --
|
||||||
|
|
||||||
|
|
||||||
type alias Model =
|
type Model
|
||||||
{ files : List File
|
= RequestError String
|
||||||
, filter : String
|
| Loading
|
||||||
, error : Maybe String
|
| Files (List TaskStatus.Status)
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
initModel : Model
|
initModel : Model
|
||||||
initModel =
|
initModel = Loading
|
||||||
{ files =
|
|
||||||
[]
|
|
||||||
, filter = ""
|
|
||||||
, error = Nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
init : ( Model, Cmd Msg )
|
init : ( Model, Cmd Msg )
|
||||||
@@ -55,26 +41,18 @@ init =
|
|||||||
type Msg
|
type Msg
|
||||||
= Reload
|
= Reload
|
||||||
| QueryRequestResult (Result Http.Error FilesResponse)
|
| QueryRequestResult (Result Http.Error FilesResponse)
|
||||||
|
| TaskStatusMsg TaskStatus.Msg
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- Request --
|
-- Request --
|
||||||
|
type alias FilesResponse =
|
||||||
|
{ files : List TaskStatus.Status }
|
||||||
fileDecoder : Decoder File
|
|
||||||
fileDecoder =
|
|
||||||
succeed File
|
|
||||||
|> required "name" string
|
|
||||||
|> required "file_name" string
|
|
||||||
|> required "source" string
|
|
||||||
|> required "id" int
|
|
||||||
|> required "status" string
|
|
||||||
|
|
||||||
|
|
||||||
queueDecoder : Decoder FilesResponse
|
queueDecoder : Decoder FilesResponse
|
||||||
queueDecoder =
|
queueDecoder =
|
||||||
succeed FilesResponse
|
succeed FilesResponse
|
||||||
|> required "queue" (list fileDecoder)
|
|> required "files" (list TaskStatus.statusDecoder)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -85,10 +63,13 @@ update : Msg -> Model -> ( Model, Cmd Msg )
|
|||||||
update msg model =
|
update msg model =
|
||||||
case msg of
|
case msg of
|
||||||
QueryRequestResult (Ok res) ->
|
QueryRequestResult (Ok res) ->
|
||||||
( { model | files = res.queue }, Cmd.none )
|
( Files res.files, Cmd.none )
|
||||||
|
|
||||||
QueryRequestResult (Err (Http.BadBody er)) ->
|
QueryRequestResult (Err (Http.BadBody err)) ->
|
||||||
( { model | error = Just er }, Cmd.none )
|
( RequestError err, Cmd.none )
|
||||||
|
|
||||||
|
TaskStatusMsg sub_msg ->
|
||||||
|
( model, Cmd.map TaskStatusMsg <| TaskStatus.update sub_msg)
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
( model, Cmd.none )
|
( model, Cmd.none )
|
||||||
@@ -126,62 +107,6 @@ cardShadowOver =
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
viewFile : File -> Element Msg
|
|
||||||
viewFile file =
|
|
||||||
el
|
|
||||||
[ width fill
|
|
||||||
|
|
||||||
-- , Background.color (rgb 0.8 0.8 0.8)
|
|
||||||
, padding 10
|
|
||||||
, Border.rounded 3
|
|
||||||
, cardShadow
|
|
||||||
, height (px 110)
|
|
||||||
, mouseOver [ cardShadowOver ]
|
|
||||||
]
|
|
||||||
(Element.column
|
|
||||||
[ width fill
|
|
||||||
, spacing 6
|
|
||||||
]
|
|
||||||
[ Element.row
|
|
||||||
[ width fill ]
|
|
||||||
[ el
|
|
||||||
[ Region.heading 1
|
|
||||||
, alignLeft
|
|
||||||
, Font.size 24
|
|
||||||
]
|
|
||||||
(text file.name)
|
|
||||||
, el
|
|
||||||
[ Region.heading 1
|
|
||||||
, alignRight
|
|
||||||
, Font.color (rgba 0 0 0 0.6)
|
|
||||||
, Font.size 20
|
|
||||||
]
|
|
||||||
(text (String.concat [ "[", file.status, "]" ]))
|
|
||||||
]
|
|
||||||
, el
|
|
||||||
[ Region.heading 2
|
|
||||||
, Font.size 16
|
|
||||||
, spacingXY 0 10
|
|
||||||
]
|
|
||||||
(text (String.concat [ "File name: ", file.file_name ]))
|
|
||||||
, el
|
|
||||||
[ Region.heading 2
|
|
||||||
, Font.size 16
|
|
||||||
]
|
|
||||||
(text (String.concat [ "Source: ", file.source ]))
|
|
||||||
, download
|
|
||||||
[ Region.footer
|
|
||||||
, Font.color (rgb 0 0 0.8)
|
|
||||||
, Font.size 16
|
|
||||||
, alignBottom
|
|
||||||
, alignRight
|
|
||||||
]
|
|
||||||
{ url = String.concat [ "http://127.0.0.1/api/file/", String.fromInt file.id ]
|
|
||||||
, label = text "Download"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
view : Model -> Element Msg
|
view : Model -> Element Msg
|
||||||
view model =
|
view model =
|
||||||
@@ -199,19 +124,26 @@ view model =
|
|||||||
, Font.size 36
|
, Font.size 36
|
||||||
]
|
]
|
||||||
(text "Files")
|
(text "Files")
|
||||||
, case model.error of
|
, case model of
|
||||||
Just e ->
|
Loading ->
|
||||||
el
|
el
|
||||||
[ Font.color (rgb 1 0 0)
|
[ Font.color (rgb 1 0 0)
|
||||||
, Font.size 16
|
, Font.size 16
|
||||||
]
|
]
|
||||||
(text e)
|
(text "Loading")
|
||||||
|
|
||||||
Nothing ->
|
RequestError err ->
|
||||||
|
el
|
||||||
|
[ Font.color (rgb 1 0 0)
|
||||||
|
, Font.size 16
|
||||||
|
]
|
||||||
|
(text <| String.concat ["Loading", err])
|
||||||
|
|
||||||
|
Files files ->
|
||||||
Element.column
|
Element.column
|
||||||
[ width fill
|
[ width fill
|
||||||
, centerX
|
, centerX
|
||||||
, spacing 20
|
, spacing 20
|
||||||
]
|
]
|
||||||
(List.map viewFile model.files)
|
( files |> List.map TaskStatus.statusView |> List.map (Element.map TaskStatusMsg) )
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ type Msg
|
|||||||
| UpdateFormFormatChnaged Format
|
| UpdateFormFormatChnaged Format
|
||||||
| PostForm Form
|
| PostForm Form
|
||||||
| PostResult (Result Http.Error PostFormResponse)
|
| PostResult (Result Http.Error PostFormResponse)
|
||||||
| None
|
|
||||||
|
|
||||||
|
|
||||||
type AudioFormats
|
type AudioFormats
|
||||||
|
|||||||
@@ -1,22 +1,16 @@
|
|||||||
module Status exposing (Model, Msg, init, queryStatus, update, view)
|
module Status exposing (Model, Msg, init, queryStatus, update, view)
|
||||||
|
|
||||||
import Color exposing (color)
|
|
||||||
import Debug exposing (toString)
|
|
||||||
import Delay
|
import Delay
|
||||||
import Element exposing (..)
|
import Element exposing (..)
|
||||||
import Element.Background as Background
|
|
||||||
import Element.Border as Border
|
|
||||||
import Element.Font as Font
|
|
||||||
import Element.Input as Input
|
|
||||||
import File.Download exposing (url)
|
|
||||||
import Files exposing (Msg(..))
|
import Files exposing (Msg(..))
|
||||||
import Globals exposing (apiDownloadFile, apiStatus)
|
import Globals exposing (apiStatus)
|
||||||
import Http
|
import Http
|
||||||
import Json.Decode exposing (Decoder, int, list, string, succeed)
|
import Json.Decode exposing (Decoder, succeed)
|
||||||
import Json.Decode.Pipeline exposing (optional, required)
|
import Json.Decode.Pipeline exposing (required)
|
||||||
import Route exposing (Route(..))
|
import Route exposing (Route(..))
|
||||||
import String exposing (right)
|
|
||||||
import Task
|
import Task
|
||||||
|
import TaskStatus
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -26,7 +20,7 @@ import Task
|
|||||||
type Msg
|
type Msg
|
||||||
= Reload
|
= Reload
|
||||||
| StatusRequestResult (Result Http.Error StatusResponse)
|
| StatusRequestResult (Result Http.Error StatusResponse)
|
||||||
| Download String
|
| TaskStatusMsg TaskStatus.Msg
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -35,7 +29,7 @@ type Msg
|
|||||||
|
|
||||||
type alias Model =
|
type alias Model =
|
||||||
{ file_id : String
|
{ file_id : String
|
||||||
, status : Maybe Status
|
, status : Maybe TaskStatus.Status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -47,55 +41,16 @@ init file_id =
|
|||||||
|
|
||||||
-- Request
|
-- Request
|
||||||
|
|
||||||
|
|
||||||
type alias File =
|
|
||||||
{ format : String
|
|
||||||
, filename : String
|
|
||||||
, timestamp : Int
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
type alias Status =
|
|
||||||
{ file : Maybe File
|
|
||||||
, name : String
|
|
||||||
, source : String
|
|
||||||
, status : String
|
|
||||||
, status_id : Int
|
|
||||||
, format : String
|
|
||||||
, timestamp : Int
|
|
||||||
, id : String
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
type alias StatusResponse =
|
type alias StatusResponse =
|
||||||
{ status : Status }
|
{ status : TaskStatus.Status }
|
||||||
|
|
||||||
|
|
||||||
fileDecoder : Decoder File
|
|
||||||
fileDecoder =
|
|
||||||
succeed File
|
|
||||||
|> required "format" string
|
|
||||||
|> required "name" string
|
|
||||||
|> required "timestamp" int
|
|
||||||
|
|
||||||
|
|
||||||
statusDecoder : Decoder Status
|
|
||||||
statusDecoder =
|
|
||||||
succeed Status
|
|
||||||
|> optional "file" (Json.Decode.map Just fileDecoder) Nothing
|
|
||||||
|> required "name" string
|
|
||||||
|> required "source" string
|
|
||||||
|> required "status" string
|
|
||||||
|> required "status_id" int
|
|
||||||
|> required "task_file_type" string
|
|
||||||
|> required "timestamp" int
|
|
||||||
|> required "id" string
|
|
||||||
|
|
||||||
|
|
||||||
statusResponseDecoder : Decoder StatusResponse
|
statusResponseDecoder : Decoder StatusResponse
|
||||||
statusResponseDecoder =
|
statusResponseDecoder =
|
||||||
succeed StatusResponse
|
succeed StatusResponse
|
||||||
|> required "status" statusDecoder
|
|> required "status" TaskStatus.statusDecoder
|
||||||
|
|
||||||
|
|
||||||
queryStatus : String -> Cmd Msg
|
queryStatus : String -> Cmd Msg
|
||||||
@@ -110,48 +65,6 @@ queryStatus id =
|
|||||||
-- Update
|
-- Update
|
||||||
|
|
||||||
|
|
||||||
cardShadow : Attr decorative msg
|
|
||||||
cardShadow =
|
|
||||||
Border.shadow
|
|
||||||
{ offset = ( 0, 4 )
|
|
||||||
, size = 4
|
|
||||||
, blur = 8
|
|
||||||
, color = rgba 0 0 0 0.2
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
cardShadowOver : Attr decorative msg
|
|
||||||
cardShadowOver =
|
|
||||||
Border.shadow
|
|
||||||
{ offset = ( 0, 4 )
|
|
||||||
, size = 8
|
|
||||||
, blur = 16
|
|
||||||
, color = rgba 0 0 0 0.2
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
card : List (Attribute msg) -> Element msg -> Element msg
|
|
||||||
card attr element =
|
|
||||||
el
|
|
||||||
(List.concat
|
|
||||||
[ attr
|
|
||||||
, [ Border.rounded 15, cardShadow, mouseOver [ cardShadowOver ], padding 20 ]
|
|
||||||
]
|
|
||||||
)
|
|
||||||
element
|
|
||||||
|
|
||||||
|
|
||||||
innerCard : List (Attribute msg) -> Element msg -> Element msg
|
|
||||||
innerCard attr element =
|
|
||||||
el
|
|
||||||
(List.concat
|
|
||||||
[ attr
|
|
||||||
, [ Border.roundEach { topLeft = 15, topRight = 15, bottomLeft = 15, bottomRight = 15 }, padding 5 ]
|
|
||||||
]
|
|
||||||
)
|
|
||||||
element
|
|
||||||
|
|
||||||
|
|
||||||
send : msg -> Cmd msg
|
send : msg -> Cmd msg
|
||||||
send msg =
|
send msg =
|
||||||
Task.succeed msg
|
Task.succeed msg
|
||||||
@@ -170,48 +83,11 @@ update msg model =
|
|||||||
StatusRequestResult _ ->
|
StatusRequestResult _ ->
|
||||||
( { model | status = Nothing }, Cmd.none )
|
( { model | status = Nothing }, Cmd.none )
|
||||||
|
|
||||||
Download file_id ->
|
TaskStatusMsg sub_msg ->
|
||||||
( model, url <| apiDownloadFile file_id )
|
( model, Cmd.map TaskStatusMsg <| TaskStatus.update sub_msg )
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- View
|
|
||||||
--Just (Download.
|
|
||||||
|
|
||||||
|
|
||||||
statusView : Status -> Element Msg
|
|
||||||
statusView status =
|
|
||||||
card [ width fill, height shrink ]
|
|
||||||
(Element.column
|
|
||||||
[ width fill ]
|
|
||||||
[ row [ width fill, height <| px 40 ]
|
|
||||||
[ el [ Font.size 28, alignLeft ] <| text status.name
|
|
||||||
, el [ Font.size 28, alignRight ] <| text (String.concat [ "[", status.status, "]" ])
|
|
||||||
]
|
|
||||||
, link [ Font.bold, Font.underline, Font.color color.blue, Font.size 16, padding 2 ]
|
|
||||||
{ url = status.source, label = text status.source }
|
|
||||||
, el [ height <| px 10 ] none
|
|
||||||
, case status.file of
|
|
||||||
Just file ->
|
|
||||||
innerCard [ width fill, height shrink, Background.color color.grey ] <|
|
|
||||||
row [ width fill, spacing 20 ]
|
|
||||||
[ Input.button
|
|
||||||
[ Background.color color.blue
|
|
||||||
, Font.color color.white
|
|
||||||
, Border.color color.red
|
|
||||||
, paddingXY 32 16
|
|
||||||
, Border.rounded 15
|
|
||||||
]
|
|
||||||
{ onPress = Just <| Download status.id
|
|
||||||
, label = Element.text "Download"
|
|
||||||
}
|
|
||||||
, el [ Font.size 16 ] <| text file.filename
|
|
||||||
]
|
|
||||||
|
|
||||||
Nothing ->
|
|
||||||
Element.none
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
view : Model -> Element Msg
|
view : Model -> Element Msg
|
||||||
@@ -225,8 +101,8 @@ view model =
|
|||||||
]
|
]
|
||||||
[ case model.status of
|
[ case model.status of
|
||||||
Just status ->
|
Just status ->
|
||||||
statusView status
|
TaskStatus.statusView status |> Element.map TaskStatusMsg
|
||||||
|
|
||||||
Nothing ->
|
Nothing ->
|
||||||
Element.text "Test"
|
Element.text "File Not Found"
|
||||||
]
|
]
|
||||||
|
|||||||
132
Modules/Website/src/TaskStatus.elm
Normal file
132
Modules/Website/src/TaskStatus.elm
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
module TaskStatus exposing (..)
|
||||||
|
import Json.Decode exposing (Decoder, int, list, string, succeed)
|
||||||
|
import Json.Decode.Pipeline exposing (optional, required)
|
||||||
|
import Element.Background as Background
|
||||||
|
import Element.Border as Border
|
||||||
|
import Element.Font as Font
|
||||||
|
import Element.Input as Input
|
||||||
|
import Element exposing (..)
|
||||||
|
import String
|
||||||
|
import Globals exposing (apiDownloadFile)
|
||||||
|
import File.Download exposing (url)
|
||||||
|
import Color exposing (color)
|
||||||
|
|
||||||
|
type alias File =
|
||||||
|
{ format : String
|
||||||
|
, filename : String
|
||||||
|
, timestamp : Int
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias Status =
|
||||||
|
{ file : Maybe File
|
||||||
|
, name : String
|
||||||
|
, source : String
|
||||||
|
, status : String
|
||||||
|
, timestamp : Int
|
||||||
|
, id : String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fileDecoder : Decoder File
|
||||||
|
fileDecoder =
|
||||||
|
succeed File
|
||||||
|
|> required "format" string
|
||||||
|
|> required "filename" string
|
||||||
|
|> required "timestamp" int
|
||||||
|
|
||||||
|
|
||||||
|
statusDecoder : Decoder Status
|
||||||
|
statusDecoder =
|
||||||
|
succeed Status
|
||||||
|
|> optional "file" (Json.Decode.map Just fileDecoder) Nothing
|
||||||
|
|> required "name" string
|
||||||
|
|> required "source" string
|
||||||
|
|> required "status" string
|
||||||
|
|> required "timestamp" int
|
||||||
|
|> required "hash" string
|
||||||
|
|
||||||
|
type Msg
|
||||||
|
= Download String
|
||||||
|
|
||||||
|
update : Msg -> Cmd Msg
|
||||||
|
update msg =
|
||||||
|
case msg of
|
||||||
|
Download id -> url <| apiDownloadFile id
|
||||||
|
|
||||||
|
statusView : Status -> Element Msg
|
||||||
|
statusView status =
|
||||||
|
card [ width fill, height shrink ]
|
||||||
|
(Element.column
|
||||||
|
[ width fill ]
|
||||||
|
[ row [ width fill, height <| px 40 ]
|
||||||
|
[ el [ Font.size 28, alignLeft ] <| text status.name
|
||||||
|
, el [ Font.size 28, alignRight ] <| text (String.concat [ "[", status.status, "]" ])
|
||||||
|
]
|
||||||
|
, link [ Font.bold, Font.underline, Font.color color.blue, Font.size 16, padding 2 ]
|
||||||
|
{ url = status.source, label = text status.source }
|
||||||
|
, el [ height <| px 10 ] none
|
||||||
|
, case status.file of
|
||||||
|
Just file ->
|
||||||
|
innerCard [ width fill, height shrink, Background.color color.grey ] <|
|
||||||
|
row [ width fill, spacing 20 ]
|
||||||
|
[ Input.button
|
||||||
|
[ Background.color color.blue
|
||||||
|
, Font.color color.white
|
||||||
|
, Border.color color.red
|
||||||
|
, paddingXY 32 16
|
||||||
|
, Border.rounded 15
|
||||||
|
]
|
||||||
|
{ onPress = Just <| Download status.id
|
||||||
|
, label = Element.text "Download"
|
||||||
|
}
|
||||||
|
, el [ Font.size 16 ] <| text file.filename
|
||||||
|
]
|
||||||
|
|
||||||
|
Nothing ->
|
||||||
|
Element.none
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
cardShadow : Attr decorative msg
|
||||||
|
cardShadow =
|
||||||
|
Border.shadow
|
||||||
|
{ offset = ( 0, 4 )
|
||||||
|
, size = 4
|
||||||
|
, blur = 8
|
||||||
|
, color = rgba 0 0 0 0.2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
cardShadowOver : Attr decorative msg
|
||||||
|
cardShadowOver =
|
||||||
|
Border.shadow
|
||||||
|
{ offset = ( 0, 4 )
|
||||||
|
, size = 8
|
||||||
|
, blur = 16
|
||||||
|
, color = rgba 0 0 0 0.2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
card : List (Attribute msg) -> Element msg -> Element msg
|
||||||
|
card attr element =
|
||||||
|
el
|
||||||
|
(List.concat
|
||||||
|
[ attr
|
||||||
|
, [ Border.rounded 15, cardShadow, mouseOver [ cardShadowOver ], padding 20 ]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
element
|
||||||
|
|
||||||
|
|
||||||
|
innerCard : List (Attribute msg) -> Element msg -> Element msg
|
||||||
|
innerCard attr element =
|
||||||
|
el
|
||||||
|
(List.concat
|
||||||
|
[ attr
|
||||||
|
, [ Border.roundEach { topLeft = 15, topRight = 15, bottomLeft = 15, bottomRight = 15 }, padding 5 ]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
element
|
||||||
Reference in New Issue
Block a user